lib3k 0.1a

Simpiti.js

Summary

No overview generated for 'Simpiti.js'


Class Summary
Simpiti Simpiti is a simple SMTP listener (because server would be exaggerated).
Simpiti.Connection Instances of this class are used to handle client connections to the Simpiti listener as well as the retrieved message via a callback handler.

//
// Copyright (c) 2006 Tobi Schäfer
// Alle Rechte vorbehalten. All rights reserved.
//
// $Revision$
// $Author$
// $Date$
//

/**
 * Creates a new instance of the Simpiti class.
 * @class Simpiti is a simple SMTP listener (because server
 * would be exaggerated). By default it listens on the common
 * SMTP port 25 and communicates with the basic set of commands
 * as defined in {@link http://www.ietf.org/rfc/rfc0821.txt RFC 821}.
 * @param {Number} port The port number Simpiti will be
 * listening for connections. By default port 25 is used.
 * @constructor
 */
var Simpiti = function(port) {
   var self = this;
   var listener, thread, callback;
   var connections = new Array;
   
   /**
    * Starts the Simpiti listener.
    * @returns The thread the listener is running in
    * @type Thread
    */
   this.start = function() {
      listener = new java.net.ServerSocket(port || 25);
      thread = new Thread(function() {
         var socket, conn;
         while (true) {
            try {
               socket = listener.accept();
               conn = new Simpiti.Connection(socket, callback);
               self.flush();
               connections.push(conn);
            } catch (x) {
               app.logger.debug(x);
            }
         }
         return;
      });
      thread.start();
      app.log("Started Simpiti listener on port " + listener.getLocalPort());
      return thread;
   };

   /**
    * Stops the Simpiti listener.
    */
   this.stop = function() {
      for (var i in connections) {
         connections[i].close();
      }
      thread.stop();
      listener.close();
      app.log("Stopped Simpiti listener on port " + listener.getLocalPort());
      return;
   };
   
   /**
    * Sets the callback handler and method for further
    * processing any mail message received by the listener.
    * @param {Object} handler The object used as context
    * @param {String} method The name of the method contained
    * by the handler object that should be executed
    */
   this.setMessageCallback = function(handler, method) {
       callback = {handler: handler, method: method};
       return;
   };
   
   /**
    * Returns the list of connections currently open.
    * @returns All open connections of the listener
    * @type Array
    */
   this.getConnections = function() {
      self.flush();
      return connections;
   };
   
   /**
    * Removes all closed connections from the list.
    */
   this.flush = function() {
      var i = 0;
      while (i < connections.length) {
         if (connections[i].valueOf().isClosed()) {
            connections.shift();
         } else {
            i += 1;
         }
      }
      return;
   }
   
   /**
    * Returns true if the listener is running.
    * @returns False if the listener is stopped.
    * @type Boolean
    */
   this.isStarted = function() {
      return thread.isRunning();
   };
   
   /**
    * Returns true if the listener is not running.
    * @returns False if the listener is running.
    * @type Boolean
    */
   this.isStopped = function() {
      return !self.isStarted();
   };
   
   /**
    * Returns a string representation of the listener.
    */
   this.toString = function() {
      return "Simpiti SMTP listener (" + (isStarted ? "port " + 
             listener.getLocalPort() : "stopped") + ")";
   };

   return this;
};

/**
 * Creates a new instance of the Simpiti.Connection class.
 * @class Instances of this class are used to handle client
 * connections to the Simpiti listener as well as the retrieved
 * message via a callback handler.
 * @param {java.net.Socket} socket The socket used for the
 * connection.
 * @param {Object} callback The handler and method to be called
 * to further process the mail message received by the connection.
 * @constructor
 */
Simpiti.Connection = function(socket, callback) {
   var reader = new (function() {
      var buffer = new java.io.BufferedReader(
         new java.io.InputStreamReader(socket.getInputStream())
      );
      this.readln = function() {
         var str = buffer.readLine();
         app.logger.debug("Simpiti " + str);
         return str;
      };
      return this;
   });

   var writer = new (function() {
      var buffer = new java.io.PrintWriter(
         new java.io.OutputStreamWriter(socket.getOutputStream())
      );
      this.writeln = function(str) {
         buffer.println(str);
         buffer.flush();
         return;
      };
      return this;
   });

   var runner = function() {
      var message = new java.lang.StringBuffer();
      var finished = false;

      writer.writeln("220 This is Simpiti");

      var input, tokens, command;
      mainLoop:
      while (true) {
         input = reader.readln(); // FIXME: What about buffer overflows?
         if (input == null) {
            break;
         }
         tokens = new java.util.StringTokenizer(input, " ;");
         try {
            command = tokens.nextToken().toUpperCase();
         } catch (x) {
            app.logger.debug(x);
            continue;
         }
         
         switch (command) {
            case "NOOP":
            case "EHLO":
            case "HELO":
            case "RSET":
            writer.writeln("250 OK");
            break;
         
            case "MAIL":
            writer.writeln("250 OK");
            break;
         
            case "RCPT":
            writer.writeln("250 OK");
            break;
         
            case "DATA":
            writer.writeln("354 Start mail data; end with <CRLF>.<CRLF>");
            var data;
            while ((data = reader.readln()) != ".") {
               data && message.append(data);
               message.append("\n");
               if (message.length() > 1048576) { // FIXME: Make limit customizable
                  writer.writeln("552 Mail data exceeds limit");
                  break mainLoop;
               }
            }
            writer.writeln("250 OK");
            finished = true;
            break;
         
            case "QUIT":
            writer.writeln("221 Good bye");
            break mainLoop;
         
            default:
            writer.writeln("502 Unknown command " + command);
         }
      }
      socket.close();
      
      if (finished && callback && callback.handler && callback.method) {
         new Thread(callback.handler, callback.method, message.toString()) .start();
      }

      return;
   };
   
   var thread = new Thread(runner) .start();
   
   /**
    * Closes the connection.
    */
   this.close = function() {
      socket.close();
      return;
   };
   
   /**
    * Returns the socket used by this connection.
    * @returns The connection socket.
    * @type java.net.Socket
    */
   this.valueOf = function() {
      return socket;
   }
   
   /**
    * Returns a string representation of the connection.
    * @returns The socket as string.
    * @type String
    */
   this.toString = function() {
      return socket.toString();
   };
   
   return this;
};

/**
 * Parse a message retrieved by a Simpiti.Connection
 * instance. Actually, any raw e-mail message can be
 * processed with this method. The result is an object
 * structure with the message's header fields
 * as properties containing the corresponding values.
 * @returns An object structure mostly consisting of 
 * the following properties:
 * <ul>
 * <li>Date</li>
 * <li>From</li>
 * <li>To</li>
 * <li>Subject</li>
 * <li>Body</li> 
 * </ul>
 * @type Object
 */
Simpiti.parseMessage = function(message) {
   var result = new Object;
   var lines = message.split("\n");
   if (lines.length > 1) {
      var i, str, pairs;
      while (!!(str = lines.shift())) {
         pairs = str.split(/^([^ ]+): (.*)$/);
         if (pairs.length < 4 || !pairs[1] || !pairs[2]) {
            continue;
         }
         result[pairs[1].trim()] = pairs[2].trim().replace(/^<(.+@.+)>$/, "$1");
      }
      if (result.Date) {
         result.Date = new Date(result.date);
      }
   }
   result.Body = lines.join("\n");
   return result;
};

lib3k 0.1a

Documentation generated by JSDoc on Sun Mar 4 00:51:24 2007