Created
November 24, 2011 17:50
-
-
Save ovaillancourt/1391905 to your computer and use it in GitHub Desktop.
Modifications to message.js for chunks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var stream = require('stream'); | |
var util = require('util'); | |
var fs = require('fs'); | |
var os = require('os'); | |
var path = require('path'); | |
var CRLF = "\r\n"; | |
var counter = 0; | |
var generate_boundary = function() | |
{ | |
var text = ""; | |
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'()+_,-./:=?"; | |
for(var i=0; i < 69; i++) | |
text += possible.charAt(Math.floor(Math.random() * possible.length)); | |
return text; | |
}; | |
var Message = function(headers) | |
{ | |
this.attachments = []; | |
this.html = null; | |
this.header = {"message-id":"<" + (new Date()).getTime() + "." + (counter++) + "." + process.pid + "@" + os.hostname() +">"}; | |
this.content = "text/plain; charset=utf-8"; | |
for(var header in headers) | |
{ | |
// allow user to override default content-type to override charset or send a single non-text message | |
if(/^content-type$/i.test(header)) | |
{ | |
this.content = headers[header]; | |
} | |
else if(header == 'text') | |
{ | |
this.text = headers[header]; | |
} | |
else | |
{ | |
// allow any headers the user wants to set?? | |
// if(/cc|bcc|to|from|reply-to|sender|subject|date|message-id/i.test(header)) | |
this.header[header.toLowerCase()] = headers[header]; | |
} | |
} | |
}; | |
Message.prototype = | |
{ | |
attach: function(path, type, name) | |
{ | |
this.attachments.push({path:path, type:type, name:name}); | |
return this; | |
}, | |
attach_alternative: function(html, charset) | |
{ | |
this.html = {message:html, charset:charset || "utf-8"}; | |
return this; | |
}, | |
valid: function(callback) | |
{ | |
var self = this; | |
if(!self.header.from) | |
{ | |
callback(false, "message does not have a valid sender"); | |
} | |
if(!self.header.to) | |
{ | |
callback(false, "message does not have a valid recipient"); | |
} | |
else if(self.attachments.length === 0) | |
{ | |
callback(true); | |
} | |
else | |
{ | |
var failed = []; | |
self.attachments.forEach(function(attachment, index) | |
{ | |
path.exists(attachment.path, function(exists) | |
{ | |
if(!exists) | |
failed.push(attachment.path + " does not exist"); | |
if(index + 1 == self.attachments.length) | |
callback(failed.length === 0, failed.join(", ")); | |
}); | |
}); | |
} | |
}, | |
stream: function() | |
{ | |
return new MessageStream(this); | |
}, | |
read: function(callback) | |
{ | |
var buffer = ""; | |
var capture = function(data) | |
{ | |
buffer += data; | |
}; | |
var output = function(err) | |
{ | |
callback(err, buffer); | |
}; | |
var str = this.stream(); | |
str.on('data', capture); | |
str.on('end', output); | |
str.on('error', output); | |
} | |
}; | |
var MessageStream = function(message) | |
{ | |
var self = this; | |
stream.Stream.call(self); | |
self.message = message; | |
self.readable = true; | |
self.resume = null; | |
self.paused = false; | |
self.stopped = false; | |
self.stream = null; | |
var output_process = function(next) | |
{ | |
var check = function() | |
{ | |
if(self.stopped) | |
return; | |
else if(self.paused) | |
self.resume = next; | |
else | |
next(); | |
}; | |
process.nextTick(check); | |
}; | |
var output_mixed = function() | |
{ | |
var data = []; | |
var boundary = generate_boundary(); | |
self.emit('data', ["Content-Type: multipart/mixed; boundary=\"", boundary, "\"", CRLF, CRLF].join("")); | |
output_process(function() { output_message(-1, boundary); }); | |
}; | |
var output_message = function(index, boundary) | |
{ | |
var next = function() | |
{ | |
output_process(function() { output_message(index + 1, boundary); }); | |
}; | |
if(index < 0) | |
{ | |
self.emit('data', ["--", boundary, CRLF].join("")); | |
if(self.message.html) | |
{ | |
output_process(function() { output_alternatives(next); }); | |
} | |
else | |
{ | |
output_text(next); | |
} | |
} | |
else if(index < self.message.attachments.length) | |
{ | |
self.emit('data', ["--", boundary, CRLF].join("")); | |
output_process(function() { output_attachment(self.message.attachments[index], next); }); | |
} | |
else | |
{ | |
self.emit('data', ["--", boundary, "--", CRLF, CRLF].join("")); | |
self.emit('end'); | |
} | |
}; | |
var output_alternatives = function(next) | |
{ | |
var boundary = generate_boundary(); | |
self.emit('data', ["Content-Type: multipart/alternative; boundary=\"", boundary, "\"", CRLF, CRLF].join("")); | |
self.emit('data', ["--", boundary, CRLF].join("")); | |
output_text(function(){}); | |
var data = ["--", boundary, CRLF]; | |
data = data.concat(["Content-Type:text/html; charset=", self.message.html.charset, CRLF, "Content-Transfer-Encoding: base64", CRLF]); | |
data = data.concat(["Content-Disposition: inline", CRLF, CRLF]); | |
/* data = data.concat([(new Buffer(self.message.html.message)).toString("base64"), CRLF, CRLF]); */ | |
var mimechunk = 76; // MIME standard wants 76 char chunks when sending out. | |
var info = (new Buffer(self.message.html.message)).toString("base64"); | |
var leftover = info.length % mimechunk; | |
var loops = Math.round(info.length / mimechunk); | |
for(var step = 0; step < loops; step++) | |
{ | |
data = data.concat(info.substring(step*mimechunk, mimechunk*(step + 1)) + CRLF); | |
} | |
if(leftover > 0) | |
{ | |
/* console.log(info.substr(-leftover)); */ | |
data = data.concat(info.substr(-leftover) + CRLF + CRLF); | |
} | |
else | |
{ | |
data = data.concat(CRLF); | |
} | |
data = data.concat(["--", boundary, "--", CRLF, CRLF]); | |
self.emit('data', data.join("")); | |
next(); | |
}; | |
var output_attachment = function(attachment, next) | |
{ | |
var data = ["Content-Type: ", attachment.type, CRLF, "Content-Transfer-Encoding: base64", CRLF]; | |
data = data.concat(["Content-Disposition: attachment; filename=\"", attachment.name, "\"", CRLF, CRLF]); | |
self.emit('data', data.join("")); | |
var mimechunk = 76; // MIME standard wants 76 char chunks when sending out | |
var chunk = mimechunk*25*3; // 5700 | |
var buffer = new Buffer(chunk); | |
var opened = function(err, fd) | |
{ | |
if(!err) | |
{ | |
var read = function(err, bytes) | |
{ | |
if(self.paused) | |
{ | |
self.resume = function() { read(err, bytes); }; | |
} | |
else if(self.stopped) | |
{ | |
fs.close(fd); | |
} | |
else if(!err) | |
{ | |
var info = buffer.toString("base64", 0, bytes); | |
var leftover= info.length % mimechunk; | |
var loops = Math.round(info.length / mimechunk); | |
for(var step = 0; step < loops; step++) | |
{ | |
self.emit('data', info.substring(step*mimechunk, mimechunk*(step + 1)) + CRLF); | |
} | |
if(bytes == chunk) // gauranteed no leftovers | |
{ | |
fs.read(fd, buffer, 0, chunk, null, read); | |
} | |
else | |
{ | |
self.emit('data', leftover ? info.substr(-leftover) + CRLF + CRLF : CRLF); // important! | |
fs.close(fd, next); | |
} | |
} | |
else | |
{ | |
fs.close(fd); | |
self.emit('error', err); | |
} | |
}; | |
fs.read(fd, buffer, 0, chunk, null, read); | |
} | |
else | |
self.emit('error', err); | |
}; | |
fs.open(attachment.path, 'r+', opened); | |
}; | |
var output_text = function(next) | |
{ | |
var data = ["Content-Type:", self.message.content, CRLF, "Content-Transfer-Encoding: 7bit", CRLF]; | |
data = data.concat(["Content-Disposition: inline", CRLF, CRLF]); | |
data = data.concat([self.message.text || "", CRLF, CRLF]); | |
self.emit('data', data.join("")); | |
next(); | |
}; | |
var output_data = function() | |
{ | |
// are there attachments or alternatives? | |
if(self.message.attachments.length || self.message.html) | |
{ | |
self.emit('data', "MIME-Version: 1.0" + CRLF); | |
output_process(output_mixed); | |
} | |
// otherwise, you only have a text message | |
else | |
{ | |
output_text(function() { self.emit('end'); }); | |
} | |
}; | |
var output_header = function() | |
{ | |
var data = []; | |
for(var header in self.message.header) | |
{ | |
// do not output BCC in the headers... | |
if(!(/bcc/i.test(header))) | |
data = data.concat([header, ": ", self.message.header[header], CRLF]); | |
} | |
self.emit('data', data.join('')); | |
output_process(output_data); | |
}; | |
output_process(output_header); | |
return; | |
}; | |
MessageStream.prototype.pause = function() | |
{ | |
self.paused = true; | |
}; | |
MessageStream.prototype.resume = function() | |
{ | |
self.paused = false; | |
if(self.resume) | |
{ | |
var resume = self.resume; | |
self.resume = null; | |
resume(); | |
} | |
}; | |
MessageStream.prototype.destroy = function() | |
{ | |
self.stopped = true; | |
}; | |
MessageStream.prototype.destroySoon = function() | |
{ | |
self.stopped = true; | |
}; | |
util.inherits(MessageStream, stream.Stream); | |
exports.Message = Message; | |
exports.create = function(headers) | |
{ | |
return new Message(headers); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment