Skip to content

Commit

Permalink
starttls, events, and linebug fixes oh my
Browse files Browse the repository at this point in the history
  • Loading branch information
unknown authored and unknown committed Apr 27, 2011
1 parent 1034ae8 commit 76ec625
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 62 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Using *EmailMessage*

var nodemailer = require("nodemailer");

var mail = new nodemailer.EmailMessage({
var mail = nodemailer.EmailMessage({
sender: "me@example.com",
to:"you@example.com"
});
Expand Down
134 changes: 89 additions & 45 deletions lib/mail.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ exports.SMTP = {
pass: false
};

exports.CommonServers = {
gmail:[],
yahoo:[],
hotmail:[]
};

/**
* mail.sendmail -> Boolean | String
*
Expand Down Expand Up @@ -83,7 +89,8 @@ var gencounter = 0;
* Creates an object to send an e-mail.
*
* params can hold the following data
*
*
* - **server** server to send message to (will default to exports.SMTP)
* - **sender** e-mail address of the sender
* - **headers** an object with custom headers.
* `{"X-Myparam": "test", "Message-ID":"12345"}`
Expand All @@ -101,7 +108,11 @@ var gencounter = 0;
* - **debug** if set, outputs the whole communication of SMTP server to console
*
* All the params can be edited/added after defining the object
*
*
* Events:
* forward(oldAddr,newAddr) - was told to try new address by server.
* defer(addr) - server takes responsibility for delivery.
* retain(addr) - unable to send to mailbox.
* Usage:
* var em = EmailMessage();
* em.sender = '"Andris Reinman" <andris@node.ee>'
Expand All @@ -112,7 +123,9 @@ var gencounter = 0;
* NB! mail.SMTP needs to be set before sending any e-mails!
**/
function EmailMessage(params){
EventEmitter.call(this);
params = params || {};
this.server = params.server || exports.SMTP;
this.sender = params.sender;
this.headers = params.headers || {};
this.to = params.to;
Expand All @@ -130,6 +143,8 @@ function EmailMessage(params){

this.callback = null;
}
var utillib = require("util");
util.inherits(EmailMessage,EventEmitter);

/**
* mail.EmailMessage#prepareVariables() -> undefined
Expand All @@ -140,7 +155,9 @@ EmailMessage.prototype.prepareVariables = function(){
if(this.html || this.attachments.length){
this.content_multipart = true;
this.content_mixed = !!this.attachments.length;
this.content_boundary = "----NODEMAILER-"+(++gencounter)+"-"+Date.now();
//'=_' is not valid quoted printable
//'?' is not in any known base64 extension
this.content_boundary = "----NODEMAILER-?=_"+(++gencounter)+"-"+Date.now();

// defaults to multipart/mixed but if there's attachments with cid value set
// use multipart/related - mail clients hide the duplicates this way
Expand Down Expand Up @@ -275,7 +292,7 @@ EmailMessage.prototype.generateBody = function(){
}

var body_boundary = this.content_mixed?
"----NODEMAILER-"+(++gencounter)+"-"+Date.now():
"----NODEMAILER-?=_"+(++gencounter)+"-"+Date.now():
this.content_boundary,
rows = [];

Expand Down Expand Up @@ -326,7 +343,7 @@ EmailMessage.prototype.generateBody = function(){
this.attachments[i].contents:
new Buffer(this.attachments[i].contents, "utf-8"),
disposition: "attachment",
content_id: this.attachments[i].cid || ((++gencounter)+"."+Date.now()+"@"+exports.SMTP.hostname)
content_id: this.attachments[i].cid || ((++gencounter)+"."+Date.now()+"@"+this.SERVER.hostname)
};


Expand All @@ -339,8 +356,9 @@ EmailMessage.prototype.generateBody = function(){
rows.push("Content-Transfer-Encoding: base64");
rows.push("");

// rows can't be too long, so base64 string will be cut to 78 char lines
rows.push(current.contents.toString("base64").replace(/(.{78})/g,"$1\r\n"));
// quoted printable rfc says limit of a line is 76 (no say on whether that includes line breaks)
// matching by leaving 2 for line break
rows.push(current.contents.toString("base64").replace(/.{74}/g,"$&\r\n"));

}

Expand Down Expand Up @@ -445,56 +463,82 @@ EmailMessage.prototype.send = function(callback){
return;
}

var mail = this;
// use SMTP
var smtp = new SMTPClient(exports.SMTP.host, exports.SMTP.port, {
hostname: exports.SMTP.hostname,
use_authentication: exports.SMTP.use_authentication,
user: exports.SMTP.user,
pass: exports.SMTP.pass,
ssl: exports.SMTP.ssl,
var smtp = new SMTPClient(this.SMTP.host, this.SMTP.port, {
hostname: this.SMTP.hostname,
use_authentication: this.SMTP.use_authentication,
user: this.SMTP.user,
pass: this.SMTP.pass,
ssl: this.SMTP.ssl,
debug: this.debug
});

smtp.on("error", function(error){
callback && callback(error, null);
});

var commands = [];
if(this.fromAddress){
// should run once
for(var i=0; i<this.fromAddress.length; i++){
commands.push("MAIL FROM:<"+this.fromAddress[i]+">");
}
}
if(this.toAddress){
// should run once
for(var i=0; i<this.toAddress.length; i++){
commands.push("RCPT TO:<"+this.toAddress[i]+">");
}
}
commands.push("DATA");

process.nextTick(runCommands);

// performs a waterfall of SMTP commands
function runCommands(){
var command = commands.shift();
if(command){
smtp.send(command, function(error, message){
if(!error){
//console.log("Command '"+command+"' sent, response:\n"+message);
process.nextTick(runCommands);
}else{
//console.log("Command '"+command+"' ended with error\n"+error.message);
var i = 0;
//concat if you need to do forwards
var toAddress = this.toAddress.concat();
var fromAddress = this.fromAddress.concat();
function nextSender() {
if(i === fromAddress.length) {
i = 0;
nextRecipient();
}
else {
smtp.send("MAIL FROM:<"+fromAddress[i++]+">", function(error, message) {
if(error) {
smtp.close();
process.nextTick(function(){
callback && callback(error, null);
});
}
});
}else
process.nextTick(sendBody);
return;
}
process.nextTick(nextSender);
});
}
})
}
function nextRecipient() {
if(i === toAddress.length) {
smtp.send("DATA",function(error, message) {
process.nextTick(sendBody);
});
}
else {
smtp.send("RCPT TO:<"+toAddress[i++]+">", function(error, message) {
if(error) {
var forwardAddress;
//Empty addresses are valid (for server sent notifications).
if(forwardAddress = error.message.match(/^551.*try\s+([<][^>]*[>])/)) {
mail.emit("forward",toAddress[i-1],forwardAddress[1]);
toAddress.splice(i,0,forwardAddress[1])
process.nextTick(nextRecipient);
return;
}
//Not all error codes are true failures (we may be able to still send it to other recipients)
else if(error.message.match(/^(?:552|451|452|500|503|421)/)){
smtp.close();
process.nextTick(function(){
callback && callback(error, null);
});
return;
}
else {
mail.emit("retain",toAddress[i-1]);
process.nextTick(nextRecipient);
return;
}
}
if(message && /^251/.test(message.test)) {
mail.emit("defer",toAddress[i-1]);
}
process.nextTick(nextRecipient);
});
}
}
process.nextTick(nextSender);

// Sends e-mail body to the SMTP server and finishes up
function sendBody(){
Expand Down
1 change: 1 addition & 0 deletions lib/mime.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ this.encodeQuotedPrintable = function(str, mimeWord, charset){
if(!mimeWord){
// lines might not be longer than 76 bytes, soft break: "=\r\n"
var lines = str.split(/\r?\n/);
str.replace(/(.{73}(?!\r?\n))/,"$&=\r\n")
for(var i=0, len = lines.length; i<len; i++){
if(lines[i].length>76){
lines[i] = this.foldLine(lines[i],76, false, true).replace(/\r\n/g,"=\r\n");
Expand Down
61 changes: 45 additions & 16 deletions lib/smtp.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ function SMTPClient(host, port, options){
this._connected = false; // Indicates if an active connection is available
this._connection = false; // Holds connection info
this._callbackQueue = []; // Queues the responses FIFO (needed for pipelining)
this._data_remainder = []; // Needed to group multi-line messages from server
this._data_remainder = ""; // Needed to group multi-line messages from server, string buffer to prevent newline issue.
this._timeoutTimer = null;
}
// Needed to convert this constructor into EventEmitter
Expand Down Expand Up @@ -249,14 +249,15 @@ SMTPClient.prototype._loginHandler = function(callback){
**/
SMTPClient.prototype._dataListener = function(data){
var action = this._callbackQueue.shift();
var isError = +data.trim().charAt(0)>3;
if(action && action.callback){
if(parseInt(data.trim().charAt(0),10)>3){
if(isError){
action.callback(new Error(data), null);
}else{
action.callback(null, data);
}
}else{
if(parseInt(data.trim().charAt(0),10)>3){
if(isError){
this.emit("error", new Error(data));
this.close();
}else{
Expand Down Expand Up @@ -294,6 +295,34 @@ SMTPClient.prototype._handshakeListener = function(data, callback){
}
}

/**
* smtp.SMTPClient#_handshakeListener(data) -> undefined
* - data(String): String received from the server
*
* Server data listener for the handshake - waits for the 220 response
* from the server (connection established).
**/
SMTPClient.prototype._starttlsHandler = function(callback){
if(this.debug)
console.log("STARTTLS: "+data.toString("utf-8").trim());
// fallback to HELO
if(this._connection.authorized) {
callback();
}
else {
this._sendCommand("STARTTLS", (function(error, data){
if(error){
this.emit("error", error);
this.close();
return;
}
starttls.wrap(this._connection,this.options,function(){
this._loginHandler(callback);
});
}).bind(this));
}
}

/**
* smtp.SMTPClient#_handshake(callback) -> undefined
* - callback (Function): will be forwarded to login after successful connection
Expand All @@ -309,7 +338,7 @@ SMTPClient.prototype._handshake = function(callback){
if(error){

// fallback to HELO
return this._sendCommand("HELO "+this.hostname, (function(error, data){
this._sendCommand("HELO "+this.hostname, (function(error, data){
if(error){
this.emit("error", error);
this.close();
Expand All @@ -329,6 +358,10 @@ SMTPClient.prototype._handshake = function(callback){
// check for TLS support
if(data.match(/STARTTLS/i)){
this.remote_starttls = true;
if(!this._connection.authorized) {
this._starttlsHandler();
return;
}
}

// check login after successful handshake
Expand Down Expand Up @@ -367,18 +400,14 @@ SMTPClient.prototype._onData = function(callback, data){
if(this.debug)
console.log("RECEIVE:\n"+JSON.stringify(data.toString("utf-8")));

var lines = data.toString("utf-8").split("\r\n"), i, length, parts;
for(i=0, length=lines.length; i<length; i++){
if(!lines[i].trim())
continue;

this._data_remainder.push(lines[i]);

parts = lines[i].match(/^\d+(.)/);
if(parts && parts[1]==" "){
this._dataListener(this._data_remainder.join("\r\n"));
this._data_remainder = [];
}
//Have to do a concat to prevent the split issue where a data packet splits a line
//IE: "1-\r\n2 " with data packets "1" "-\r\n2 ", using the split method
// becomes ["1","-","2 "]
this._data_remainder = (data.toString("utf-8") + this._data_remainder);
var match = /(^\d+.*$)*?(^\d+[\ ].*$)/m.exec(this._data_remainder);
if(match && match.index === 0) {
this._dataListener(match[0].replace(/^\s*$/mg,""));
this._data_remainder = slice(match[0].length);
}
}

Expand Down

0 comments on commit 76ec625

Please sign in to comment.