Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

mailparser

Package Overview
Dependencies
Maintainers
1
Versions
112
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mailparser - npm Package Compare versions

Comparing version 0.2.4 to 0.2.6

jsdoc/symbols/inherited.gif

568

lib/mailparser.js

@@ -5,3 +5,3 @@

* @author <a href="mailto:andris@node.ee">Andris Reinman</a>
* @version 0.2.3
* @version 0.2.6
*/

@@ -28,4 +28,12 @@

/**
* Creates instance of MailParser which in turn extends Stream
* <p>Creates instance of MailParser which in turn extends Stream</p>
*
* <p>Options object has the following properties:</p>
*
* <ul>
* <li><b>debug</b> - if set to true print all incoming lines to console</li>
* <li><b>streamAttachments</b> - if set to true, stream attachments instead of including them</li>
* <li><b>unescapeSMTP</b> - if set to true replace double dots in the beginning of the file</li>
* </ul>
*
* @constructor

@@ -40,12 +48,42 @@ * @param {Object} [options] Optional options object

/** @private*/ this.options = options || {};
/**
* Options object
* @public */ this.options = options || {};
/** @private */ this.state = STATES.header;
/** @private */ this.remainder = "";
/** @public */ this.mimeTree = this.createMimeNode();
/** @private */ this.currentNode = this.mimeTree;
/** @private */ this.fileNames = {};
/** @private */ this.multipartTree = [];
/** @private */ this.iconv = {};
/** @private */ this.mailData = {};
/**
* Indicates current state the parser is in
* @private */ this._state = STATES.header;
/**
* The remaining data from the previos chunk which is waiting to be processed
* @private */ this._remainder = "";
/**
* The complete tree structure of the e-mail
* @public */ this.mimeTree = this._createMimeNode();
/**
* Current node of the multipart mime tree that is being processed
* @private */ this._currentNode = this.mimeTree;
/**
* An object of already used attachment filenames
* @private */ this._fileNames = {};
/**
* An array of multipart nodes
* @private */ this._multipartTree = [];
/**
* Cache for iconv converter objects
* @private */ this._iconv = {};
/**
* This is the final mail structure object that is returned to the client
* @public */ this.mailData = {};
/**
* Line counter for debugging
* @private */ this._linecounter = 0;
}

@@ -56,3 +94,3 @@ // inherit methods and properties of Stream

/**
* Writes a value to the MailParser stream
* <p>Writes a value to the MailParser stream<p>
*

@@ -69,4 +107,4 @@ * @param {Buffer|String} chunk The data to be written to the MailParser stream

if(chunk && chunk.length){
this.remainder += chunk.toString("binary");
process.nextTick(this.process.bind(this));
this._remainder += chunk.toString("binary");
process.nextTick(this._process.bind(this));
return true;

@@ -79,5 +117,5 @@ }else{

/**
* Terminates the MailParser stream
* <p>Terminates the MailParser stream</p>
*
* If "chunk" is set, writes it to the Stream before terminating.
* <p>If "chunk" is set, writes it to the Stream before terminating.</p>
*

@@ -92,38 +130,35 @@ * @param {Buffer|String} chunk The data to be written to the MailParser stream

if(this.options.debug && this.remainder){
console.log("REMAINDER: "+this.remainder)
if(this.options.debug && this._remainder){
console.log("REMAINDER: "+this._remainder)
}
if(chunk){
this.remainder += chunk.toString("binary");
this._remainder += chunk.toString("binary");
}
process.nextTick(this.process.bind(this, true));
process.nextTick(this._process.bind(this, true));
};
/**
* Processes the data written to the MailParser stream
* <p>Processes the data written to the MailParser stream</p>
*
* The data is split into lines and each line is processed individually. Last
* <p>The data is split into lines and each line is processed individually. Last
* line in the batch is preserved as a remainder since it is probably not a
* complete line but just the beginning of it. The remainder is later prepended
* to the next batch of data.
* to the next batch of data.</p>
*
* If "lastLine" is set to true,
*
* @param {Boolean} [finalPart=false] if set to true indicates that this is the last part of the stream
*/
var c =0;
MailParser.prototype.process = function(finalPart){
MailParser.prototype._process = function(finalPart){
finalPart = !!finalPart;
var lines = this.remainder.split(/\r?\n|\r/),
var lines = this._remainder.split(/\r?\n|\r/),
line, i, len;
if(!finalPart){
this.remainder = lines.pop();
this._remainder = lines.pop();
// force line to 1MB chunks if needed
if(this.remainder.length>1048576){
this.remainder = this.remainder.replace(/(.{1048576}(?!\r?\n|\r))/g,"$&\n");
if(this._remainder.length>1048576){
this._remainder = this._remainder.replace(/(.{1048576}(?!\r?\n|\r))/g,"$&\n");
}

@@ -135,8 +170,12 @@ }

if(this.options.unescapeSMTP && line.substr(0,2)==".."){
line = line.substr(1);
}
if(this.options.debug){
console.log("LINE " + (++c) + " ("+this.state+"): "+line);
console.log("LINE " + (++this._linecounter) + " ("+this._state+"): "+line);
}
if(this.state == STATES.header){
if(this.processStateHeader(line) === true){
if(this._state == STATES.header){
if(this._processStateHeader(line) === true){
continue;

@@ -146,5 +185,5 @@ }

if(this.state == STATES.body){
if(this._state == STATES.body){
if(this.processStateBody(line) === true){
if(this._processStateBody(line) === true){
continue;

@@ -157,4 +196,7 @@ }

if(finalPart){
this.state = STATES.finished;
process.nextTick(this.processMimeTree.bind(this));
if(this._currentNode.content || this._currentNode.stream){
this._finalizeContents();
}
this._state = STATES.finished;
process.nextTick(this._processMimeTree.bind(this));
}

@@ -164,6 +206,6 @@ };

/**
* Processes a line while in header state
* <p>Processes a line while in header state</p>
*
* If header state ends and body starts, detect if the contents is an attachment
* and create a stream for it if needed
* <p>If header state ends and body starts, detect if the contents is an attachment
* and create a stream for it if needed</p>
*

@@ -173,5 +215,5 @@ * @param {String} line The contents of a line to be processed

*/
MailParser.prototype.processStateHeader = function(line){
MailParser.prototype._processStateHeader = function(line){
var boundary, i, len, attachment,
lastPos = this.currentNode.headers.length - 1,
lastPos = this._currentNode.headers.length - 1,
textContent = false;

@@ -181,32 +223,32 @@

if(!line.length){
this.state = STATES.body;
this._state = STATES.body;
// if there's unprocessed header data, do it now
if(lastPos >= 0){
this.processHeaderLine(lastPos);
this._processHeaderLine(lastPos);
}
// this is a very simple e-mail, no content type set
if(!this.currentNode.parentNode && !this.currentNode.meta.contentType){
this.currentNode.meta.contentType = "text/plain";
if(!this._currentNode.parentNode && !this._currentNode.meta.contentType){
this._currentNode.meta.contentType = "text/plain";
}
textContent = ["text/plain", "text/html"].indexOf(this.currentNode.meta.contentType || "") >= 0;
textContent = ["text/plain", "text/html"].indexOf(this._currentNode.meta.contentType || "") >= 0;
// detect if this is an attachment or a text node (some agents use inline dispositions for text)
if(textContent && (!this.currentNode.meta.contentDisposition || this.currentNode.meta.contentDisposition == "inline")){
this.currentNode.attachment = false;
}else if((!textContent || ["attachment", "inline"].indexOf(this.currentNode.meta.contentDisposition)>=0) &&
!this.currentNode.meta.mimeMultipart){
this.currentNode.attachment = true;
if(textContent && (!this._currentNode.meta.contentDisposition || this._currentNode.meta.contentDisposition == "inline")){
this._currentNode.attachment = false;
}else if((!textContent || ["attachment", "inline"].indexOf(this._currentNode.meta.contentDisposition)>=0) &&
!this._currentNode.meta.mimeMultipart){
this._currentNode.attachment = true;
}
// handle attachment start
if(this.currentNode.attachment){
if(this._currentNode.attachment){
this.currentNode.meta.length = 0;
this.currentNode.checksum = crypto.createHash("md5");
this._currentNode.meta.length = 0;
this._currentNode.checksum = crypto.createHash("md5");
fileName = this.currentNode.meta.fileName || "attachment";
this.currentNode.meta.generatedFileName = this.generateFileName(fileName);
fileName = this._currentNode.meta.fileName || "attachment";
this._currentNode.meta.generatedFileName = this._generateFileName(fileName);

@@ -218,26 +260,26 @@ attachment = {

if(this.options.streamAttachments){
if(this.currentNode.meta.contentType){
attachment.contentType = this.currentNode.meta.contentType || null;
if(this._currentNode.meta.contentType){
attachment.contentType = this._currentNode.meta.contentType || null;
}
if(this.currentNode.meta.contentId){
attachment.contentId = this.currentNode.meta.contentId || "";
if(this._currentNode.meta.contentId){
attachment.contentId = this._currentNode.meta.contentId || "";
}
if(this.currentNode.meta.charset){
attachment.charset = this.currentNode.meta.charset || "utf-8";
if(this._currentNode.meta.charset){
attachment.charset = this._currentNode.meta.charset || "utf-8";
}
if(this.currentNode.meta.transferEncoding == "base64"){
this.currentNode.stream = new Streams.Base64Stream();
}else if(this.currentNode.meta.transferEncoding == "quoted-printable"){
this.currentNode.stream = new Streams.QPStream("binary");
if(this._currentNode.meta.transferEncoding == "base64"){
this._currentNode.stream = new Streams.Base64Stream();
}else if(this._currentNode.meta.transferEncoding == "quoted-printable"){
this._currentNode.stream = new Streams.QPStream("binary");
}else{
this.currentNode.stream = new Streams.BinaryStream();
this._currentNode.stream = new Streams.BinaryStream();
}
attachment.stream = this.currentNode.stream;
attachment.stream = this._currentNode.stream;
this.emit("attachment", attachment);
}else{
this.currentNode.content = undefined;
this._currentNode.content = undefined;
}

@@ -251,8 +293,8 @@ }

if(line.match(/^\s+/) && lastPos>=0){
this.currentNode.headers[lastPos] += " " + line.trim();
this._currentNode.headers[lastPos] += " " + line.trim();
}else{
this.currentNode.headers.push(line.trim());
this._currentNode.headers.push(line.trim());
if(lastPos>=0){
// if a complete header line is received, process it
this.processHeaderLine(lastPos);
this._processHeaderLine(lastPos);
}

@@ -265,3 +307,3 @@ }

/**
* Processes a line while in body state
* <p>Processes a line while in body state</p>
*

@@ -271,3 +313,3 @@ * @param {String} line The contents of a line to be processed

*/
MailParser.prototype.processStateBody = function(line){
MailParser.prototype._processStateBody = function(line){
var i, len, node,

@@ -278,15 +320,15 @@ nodeReady = false;

if(line.substr(0, 2) == "--"){
for(i=0, len = this.multipartTree.length; i<len; i++){
for(i=0, len = this._multipartTree.length; i<len; i++){
// check if a new element block starts
if(line == "--" + this.multipartTree[i].boundary){
if(line == "--" + this._multipartTree[i].boundary){
if(this.currentNode.content || this.currentNode.stream){
this.finalizeContents();
if(this._currentNode.content || this._currentNode.stream){
this._finalizeContents();
}
node = this.createMimeNode(this.multipartTree[i].node);
this.multipartTree[i].node.childNodes.push(node);
this.currentNode = node;
this.state = STATES.header;
node = this._createMimeNode(this._multipartTree[i].node);
this._multipartTree[i].node.childNodes.push(node);
this._currentNode = node;
this._state = STATES.header;
nodeReady = true;

@@ -296,14 +338,14 @@ break;

// check if a multipart block ends
if(line == "--" + this.multipartTree[i].boundary + "--"){
if(line == "--" + this._multipartTree[i].boundary + "--"){
if(this.currentNode.content || this.currentNode.stream){
this.finalizeContents();
if(this._currentNode.content || this._currentNode.stream){
this._finalizeContents();
}
if(this.multipartTree[i].node.parentNode){
this.currentNode = this.multipartTree[i].node.parentNode;
if(this._multipartTree[i].node.parentNode){
this._currentNode = this._multipartTree[i].node.parentNode;
}else{
this.currentNode = this.multipartTree[i].node;
this._currentNode = this._multipartTree[i].node;
}
this.state = STATES.body;
this._state = STATES.body;
nodeReady = true;

@@ -319,7 +361,7 @@ break;

// handle text or attachment line
if(["text/plain", "text/html"].indexOf(this.currentNode.meta.contentType || "")>=0 &&
!this.currentNode.attachment){
this.handleTextLine(line);
}else if(this.currentNode.attachment){
this.handleAttachmentLine(line);
if(["text/plain", "text/html"].indexOf(this._currentNode.meta.contentType || "")>=0 &&
!this._currentNode.attachment){
this._handleTextLine(line);
}else if(this._currentNode.attachment){
this._handleAttachmentLine(line);
}

@@ -331,14 +373,14 @@

/**
* Processes a complete unfolded header line
* <p>Processes a complete unfolded header line</p>
*
* Processes a line from current node headers array and replaces its value.
* <p>Processes a line from current node headers array and replaces its value.
* Input string is in the form of "X-Mailer: PHP" and its replacement would be
* an object {key: "x-mailer", value: "PHP"}
* an object <code>{key: "x-mailer", value: "PHP"}</code></p>
*
* Additionally node meta object will be filled also, for example with data from
* To: From: Cc: etc fields.
* <p>Additionally node meta object will be filled also, for example with data from
* To: From: Cc: etc fields.</p>
*
* @param {Number} pos Which header element (from an header lines array) should be processed
*/
MailParser.prototype.processHeaderLine = function(pos){
MailParser.prototype._processHeaderLine = function(pos){
var key, value, parts, line;

@@ -348,3 +390,3 @@

if(!(line = this.currentNode.headers[pos])){
if(!(line = this._currentNode.headers[pos])){
return;

@@ -360,21 +402,21 @@ }

case "content-type":
value = this.parseContentType(value);
value = this._parseContentType(value);
break;
case "mime-version":
this.currentNode.useMIME = true;
this._currentNode.useMIME = true;
break;
case "date":
this.currentNode.meta.date = new Date(datetime.strtotime(value)*1000 || Date.now());
this._currentNode.meta.date = new Date(datetime.strtotime(value)*1000 || Date.now());
break;
case "to":
this.currentNode.to = mimelib.parseAddresses(value);
this._currentNode.to = mimelib.parseAddresses(value);
break;
case "from":
this.currentNode.from = mimelib.parseAddresses(value);
this._currentNode.from = mimelib.parseAddresses(value);
break;
case "cc":
this.currentNode.cc = mimelib.parseAddresses(value);
this._currentNode.cc = mimelib.parseAddresses(value);
break;
case "bcc":
this.currentNode.bcc = mimelib.parseAddresses(value);
this._currentNode.bcc = mimelib.parseAddresses(value);
break;

@@ -384,38 +426,38 @@ case "x-priority":

case "importance":
value = this.parsePriority(value);
value = this._parsePriority(value);
break;
case "message-id":
this.currentNode.meta.messageId = this.trimQuotes(value);
this._currentNode.meta.messageId = this._trimQuotes(value);
break;
case "references":
this.currentNode.meta.messageReferences = this.trimQuotes(value);
this._currentNode.meta.messageReferences = this._trimQuotes(value);
break;
case "in-reply-to":
this.currentNode.meta.inReplyTo = this.trimQuotes(value);
this._currentNode.meta.inReplyTo = this._trimQuotes(value);
break;
case "thread-index":
this.currentNode.meta.threadIndex = value;
this._currentNode.meta.threadIndex = value;
break;
case "content-transfer-encoding":
this.currentNode.meta.transferEncoding = value.toLowerCase();
this._currentNode.meta.transferEncoding = value.toLowerCase();
break;
case "subject":
this.currentNode.subject = this.encodeString(value);
this._currentNode.subject = this._encodeString(value);
break;
case "content-disposition":
this.parseContentDisposition(value);
this._parseContentDisposition(value);
break;
case "content-id":
this.currentNode.meta.contentId = this.trimQuotes(value);
this._currentNode.meta.contentId = this._trimQuotes(value);
break;
}
this.currentNode.headers[pos] = {key: key, value: value};
this._currentNode.headers[pos] = {key: key, value: value};
};
/**
* Creates an empty node element for the mime tree
* <p>Creates an empty node element for the mime tree</p>
*
* Created element includes parentNode property and a childNodes array. This is
* needed to later walk the whole mime tree
* <p>Created element includes parentNode property and a childNodes array. This is
* needed to later walk the whole mime tree</p>
*

@@ -425,5 +467,5 @@ * @param {Object} [parentNode] the parent object for the created node

*/
MailParser.prototype.createMimeNode = function(parentNode){
MailParser.prototype._createMimeNode = function(parentNode){
var node = {
parentNode: parentNode || this.currentNode || null,
parentNode: parentNode || this._currentNode || null,
headers: [],

@@ -438,22 +480,20 @@ meta: {},

/**
* Splits a header value into key-value pairs
* <p>Splits a header value into key-value pairs</p>
*
* Splits on ";", the first value will be set as defaultValue property and will
* not be handled, others will be split on "=" to key-value pairs
* <p>Splits on <code>;</code> - the first value will be set as <code>defaultValue</code> property and will
* not be handled, others will be split on <code>=</code> to key-value pairs</p>
*
* For example
* <p>For example <code>content-type: text/plain; charset=utf-8</code> will become:</p>
*
* content-type: text/plain; charset=utf-8
* <pre>
* {
* defaultValue: "text/plain",
* charset: "utf-8"
* }
* </pre>
*
* Will become
*
* {
* defaultValue: "text/plain",
* charset: "utf-8"
* }
*
* @param {String} value A string to be splitted into key-value pairs
* @returns {Object} a key-value object, with defaultvalue property
*/
MailParser.prototype.parseHeaderLineWithParams = function(value){
MailParser.prototype._parseHeaderLineWithParams = function(value){
var key, parts, returnValue = {};

@@ -470,3 +510,3 @@

// trim quotes
value = this.trimQuotes(value);
value = this._trimQuotes(value);
returnValue[key] = value;

@@ -479,6 +519,6 @@ }

/**
* Parses a Content-Type header field value
* <p>Parses a Content-Type header field value</p>
*
* Fetches additional properties from the content type (charset etc.) and fills
* current node meta object with this data
* <p>Fetches additional properties from the content type (charset etc.) and fills
* current node meta object with this data</p>
*

@@ -488,13 +528,13 @@ * @param {String} value Content-Type string

*/
MailParser.prototype.parseContentType = function(value){
value = this.parseHeaderLineWithParams(value);
MailParser.prototype._parseContentType = function(value){
value = this._parseHeaderLineWithParams(value);
if(value){
if(value.defaultValue){
value.defaultValue = value.defaultValue.toLowerCase();
this.currentNode.meta.contentType = value.defaultValue;
this._currentNode.meta.contentType = value.defaultValue;
if(value.defaultValue.substr(0,"multipart/".length)=="multipart/"){
this.currentNode.meta.mimeMultipart = value.defaultValue.substr("multipart/".length);
this._currentNode.meta.mimeMultipart = value.defaultValue.substr("multipart/".length);
}
}else{
this.currentNode.meta.contentType = "application/octet-stream";
this._currentNode.meta.contentType = "application/octet-stream";
}

@@ -510,18 +550,18 @@ if(value.charset){

}
this.currentNode.meta.charset = value.charset;
this._currentNode.meta.charset = value.charset;
}
if(value.format){
this.currentNode.meta.textFormat = value.format;
this._currentNode.meta.textFormat = value.format;
}
if(value.boundary){
this.currentNode.meta.mimeBoundary = value.boundary;
this._currentNode.meta.mimeBoundary = value.boundary;
}
if(value.name && !this.currentNode.meta.fileName){
this.currentNode.meta.fileName = this.replaceMimeWords(value.name);
if(value.name && !this._currentNode.meta.fileName){
this._currentNode.meta.fileName = this._replaceMimeWords(value.name);
}
if(value.boundary){
this.currentNode.meta.mimeBoundary = value.boundary;
this.multipartTree.push({
this._currentNode.meta.mimeBoundary = value.boundary;
this._multipartTree.push({
boundary: value.boundary,
node: this.currentNode
node: this._currentNode
});

@@ -534,19 +574,19 @@ }

/**
* Parses Content-Disposition header field value
* <p>Parses Content-Disposition header field value</p>
*
* Fetches filename to current node meta object
* <p>Fetches filename to current node meta object</p>
*
* @param {String} value A Content-Disposition header field
*/
MailParser.prototype.parseContentDisposition = function(value){
MailParser.prototype._parseContentDisposition = function(value){
var returnValue = {};
value = this.parseHeaderLineWithParams(value);
value = this._parseHeaderLineWithParams(value);
if(value){
if(value.defaultValue){
this.currentNode.meta.contentDisposition = value.defaultValue.trim().toLowerCase();
this._currentNode.meta.contentDisposition = value.defaultValue.trim().toLowerCase();
}
if(value.filename){
this.currentNode.meta.fileName = this.replaceMimeWords(value.filename);
this._currentNode.meta.fileName = this._replaceMimeWords(value.filename);
}

@@ -557,3 +597,3 @@ }

/**
* Parses the priority of the e-mail
* <p>Parses the priority of the e-mail</p>
*

@@ -563,3 +603,3 @@ * @param {String} value The priority value

*/
MailParser.prototype.parsePriority = function(value){
MailParser.prototype._parsePriority = function(value){
value = value.toLowerCase().trim();

@@ -589,30 +629,30 @@ if(!isNaN(value)){

/**
* Processes a line in text/html or text/plain node
* <p>Processes a line in text/html or text/plain node</p>
*
* Append the line to the content property
* <p>Append the line to the content property</p>
*
* @param {String} line A line to be processed
*/
MailParser.prototype.handleTextLine = function(line){
MailParser.prototype._handleTextLine = function(line){
if(["quoted-printable", "base64"].indexOf(this.currentNode.meta.transferEncoding)>=0){
if(typeof this.currentNode.content != "string"){
this.currentNode.content = line;
if(["quoted-printable", "base64"].indexOf(this._currentNode.meta.transferEncoding)>=0){
if(typeof this._currentNode.content != "string"){
this._currentNode.content = line;
}else{
this.currentNode.content += "\n"+line;
this._currentNode.content += "\n"+line;
}
}else{
if(this.currentNode.meta.textFormat != "flowed"){
if(typeof this.currentNode.content != "string"){
this.currentNode.content = this.encodeString(line);
if(this._currentNode.meta.textFormat != "flowed"){
if(typeof this._currentNode.content != "string"){
this._currentNode.content = this._encodeString(line);
}else{
this.currentNode.content += "\n" + this.encodeString(line);
this._currentNode.content += "\n" + this._encodeString(line);
}
}else{
if(typeof this.currentNode.content != "string"){
this.currentNode.content = this.encodeString(line);
}else if(this.currentNode.content.match(/[ ]{1,}$/)){
this.currentNode.content += this.encodeString(line);
if(typeof this._currentNode.content != "string"){
this._currentNode.content = this._encodeString(line);
}else if(this._currentNode.content.match(/[ ]{1,}$/)){
this._currentNode.content += this._encodeString(line);
}else{
this.currentNode.content += "\n"+this.encodeString(line);
this._currentNode.content += "\n"+this._encodeString(line);
}

@@ -624,20 +664,20 @@ }

/**
* Processes a line in an attachment node
* <p>Processes a line in an attachment node</p>
*
* If a stream is set up for the attachment write the line to the
* stream as a Buffer object, otherwise append it to the content property
* <p>If a stream is set up for the attachment write the line to the
* stream as a Buffer object, otherwise append it to the content property</p>
*
* @param {String} line A line to be processed
*/
MailParser.prototype.handleAttachmentLine = function(line){
if(!this.currentNode.attachment){
MailParser.prototype._handleAttachmentLine = function(line){
if(!this._currentNode.attachment){
return;
}
if(this.currentNode.stream){
this.currentNode.stream.write(new Buffer(line, "binary"));
}else if("content" in this.currentNode){
if(typeof this.currentNode.content!="string"){
this.currentNode.content = line;
if(this._currentNode.stream){
this._currentNode.stream.write(new Buffer(line, "binary"));
}else if("content" in this._currentNode){
if(typeof this._currentNode.content!="string"){
this._currentNode.content = line;
}else{
this.currentNode.content += "\r\n" + line;
this._currentNode.content += "\r\n" + line;
}

@@ -648,29 +688,29 @@ }

/**
* Finalizes a node processing
* <p>Finalizes a node processing</p>
*
* If the node is a text/plain or text/html, convert it to UTF-8 encoded string
* <p>If the node is a text/plain or text/html, convert it to UTF-8 encoded string
* If it is an attachment, convert it to a Buffer or if an attachment stream is
* set up, close the stream
* set up, close the stream</p>
*/
MailParser.prototype.finalizeContents = function(){
MailParser.prototype._finalizeContents = function(){
var streamInfo;
if(this.currentNode.content){
if(!this.currentNode.attachment){
if(this.currentNode.meta.transferEncoding == "quoted-printable"){
this.currentNode.content = mimelib.decodeQuotedPrintable(this.currentNode.content, false, this.currentNode.meta.charset);
}else if(this.currentNode.meta.transferEncoding == "base64"){
this.currentNode.content = mimelib.decodeBase64(this.currentNode.content, false, this.currentNode.meta.charset);
if(this._currentNode.content){
if(!this._currentNode.attachment){
if(this._currentNode.meta.transferEncoding == "quoted-printable"){
this._currentNode.content = mimelib.decodeQuotedPrintable(this._currentNode.content, false, this._currentNode.meta.charset);
}else if(this._currentNode.meta.transferEncoding == "base64"){
this._currentNode.content = mimelib.decodeBase64(this._currentNode.content, false, this._currentNode.meta.charset);
}
}else{
if(this.currentNode.meta.transferEncoding == "quoted-printable"){
this.currentNode.content = mimelib.decodeQuotedPrintable(this.currentNode.content, false, "binary");
}else if(this.currentNode.meta.transferEncoding == "base64"){
this.currentNode.content = new Buffer(this.currentNode.content.replace(/[^\w\+\/=]/g,''), "base64");
if(this._currentNode.meta.transferEncoding == "quoted-printable"){
this._currentNode.content = mimelib.decodeQuotedPrintable(this._currentNode.content, false, "binary");
}else if(this._currentNode.meta.transferEncoding == "base64"){
this._currentNode.content = new Buffer(this._currentNode.content.replace(/[^\w\+\/=]/g,''), "base64");
}else{
this.currentNode.content = new Buffer(this.currentNode.content, "binary");
this._currentNode.content = new Buffer(this._currentNode.content, "binary");
}
this.currentNode.checksum.update(this.currentNode.content);
this.currentNode.meta.checksum = this.currentNode.checksum.digest("hex");
this.currentNode.meta.length = this.currentNode.content.length;
this._currentNode.checksum.update(this._currentNode.content);
this._currentNode.meta.checksum = this._currentNode.checksum.digest("hex");
this._currentNode.meta.length = this._currentNode.content.length;
}

@@ -680,9 +720,9 @@

if(this.currentNode.stream){
streamInfo = this.currentNode.stream.end() || {};
if(this._currentNode.stream){
streamInfo = this._currentNode.stream.end() || {};
if(streamInfo.checksum){
this.currentNode.meta.checksum = streamInfo.checksum;
this._currentNode.meta.checksum = streamInfo.checksum;
}
if(streamInfo.length){
this.currentNode.meta.length = streamInfo.length;
this._currentNode.meta.length = streamInfo.length;
}

@@ -693,11 +733,11 @@ }

/**
* Processes the mime tree
* <p>Processes the mime tree</p>
*
* Finds text parts and attachments from the tree. If there's several text/plain
* <p>Finds text parts and attachments from the tree. If there's several text/plain
* or text/html parts, push the ones from the lower parts of the tree to the
* alternatives array
* alternatives array</p>
*
* Emits "end" when finished
* <p>Emits "end" when finished</p>
*/
MailParser.prototype.processMimeTree = function(){
MailParser.prototype._processMimeTree = function(){
var level = 0, htmlLevel, textLevel, html, text,

@@ -709,5 +749,5 @@ returnValue = {}, i, len;

if(!this.mimeTree.meta.mimeMultipart){
this.processMimeNode(this.mimeTree, 0);
this._processMimeNode(this.mimeTree, 0);
}else{
this.walkMimeTree(this.mimeTree);
this._walkMimeTree(this.mimeTree);
}

@@ -792,3 +832,3 @@

/**
* Walks the mime tree and runs processMimeNode on each node of the tree
* <p>Walks the mime tree and runs processMimeNode on each node of the tree</p>
*

@@ -798,8 +838,8 @@ * @param {Object} node A mime tree node

*/
MailParser.prototype.walkMimeTree = function(node, level){
MailParser.prototype._walkMimeTree = function(node, level){
level = level || 1;
for(var i=0, len = node.childNodes.length; i<len; i++){
this.processMimeNode(node.childNodes[i], level);
this.walkMimeTree(node.childNodes[i], level+1);
this._processMimeNode(node.childNodes[i], level);
this._walkMimeTree(node.childNodes[i], level+1);
}

@@ -809,5 +849,5 @@ };

/**
* Processes of a node in the mime tree
* <p>Processes of a node in the mime tree</p>
*
* Pushes the node into appropriate this.mailData array (text/html to this.mailData.html array etc)
* <p>Pushes the node into appropriate <code>this.mailData</code> array (<code>text/html</code> to <code>this.mailData.html</code> array etc)</p>
*

@@ -817,3 +857,3 @@ * @param {Object} node A mime tree node

*/
MailParser.prototype.processMimeNode = function(node, level){
MailParser.prototype._processMimeNode = function(node, level){
level = level || 0;

@@ -824,3 +864,3 @@

case "text/html":
this.mailData.html.push({content: node.content || "", level: level});
this.mailData.html.push({content: this._updateHTMLCharset(node.content || ""), level: level});
break;

@@ -841,3 +881,3 @@ case "text/plain":

/**
* Converts a string from one charset to another
* <p>Converts a string from one charset to another</p>
*

@@ -849,3 +889,3 @@ * @param {Buffer|String} value A String to be converted

*/
MailParser.prototype.convertString = function(value, fromCharset, toCharset){
MailParser.prototype._convertString = function(value, fromCharset, toCharset){
toCharset = (toCharset || "utf-8").toUpperCase();

@@ -861,6 +901,6 @@ fromCharset = (fromCharset || "utf-8").toUpperCase();

try{ // in case there is no such charset or EINVAL occurs leave the string untouched
if(!this.iconv[fromCharset+toCharset]){
this.iconv[fromCharset+toCharset] = new Iconv(fromCharset, toCharset+'//TRANSLIT//IGNORE');
if(!this._iconv[fromCharset+toCharset]){
this._iconv[fromCharset+toCharset] = new Iconv(fromCharset, toCharset+'//TRANSLIT//IGNORE');
}
value = this.iconv[fromCharset+toCharset].convert(value);
value = this._iconv[fromCharset+toCharset].convert(value);
}catch(E){}

@@ -872,3 +912,3 @@

/**
* Encodes a header string to UTF-8
* <p>Encodes a header string to UTF-8</p>
*

@@ -878,4 +918,4 @@ * @param {String} value String to be encoded

*/
MailParser.prototype.encodeString = function(value){
value = this.replaceMimeWords(this.convertString(value, this.currentNode.meta.charset).toString("utf-8"));
MailParser.prototype._encodeString = function(value){
value = this._replaceMimeWords(this._convertString(value, this._currentNode.meta.charset).toString("utf-8"));
return value;

@@ -885,3 +925,3 @@ };

/**
* Replaces mime words in a string with UTF-8 encoded strings
* <p>Replaces mime words in a string with UTF-8 encoded strings</p>
*

@@ -891,3 +931,3 @@ * @param {String} value String to be converted

*/
MailParser.prototype.replaceMimeWords = function(value){
MailParser.prototype._replaceMimeWords = function(value){
return value.

@@ -901,3 +941,3 @@ replace(/(=\?[^?]+\?[QqBb]\?[^?]+\?=)\s+(?==\?[^?]+\?[QqBb]\?[^?]+\?=)/g, "$1"). // join mimeWords

/**
* Removes enclosing quotes ("", '', <>) from a string
* <p>Removes enclosing quotes ("", '', &lt;&gt;) from a string</p>
*

@@ -907,3 +947,3 @@ * @param {String} value String to be converted

*/
MailParser.prototype.trimQuotes = function(value){
MailParser.prototype._trimQuotes = function(value){
value = (value || "").trim();

@@ -919,9 +959,11 @@ if((value.charAt(0)=='"' && value.charAt(value.length-1)=='"') ||

/**
* Generates a context unique filename for an attachment
* <p>Generates a context unique filename for an attachment</p>
*
* If a filename already exists, append a number to it
* <p>If a filename already exists, append a number to it</p>
*
* file.txt
* file-1.txt
* file-2.txt
* <ul>
* <li>file.txt</li>
* <li>file-1.txt</li>
* <li>file-2.txt</li>
* </ul>
*

@@ -931,3 +973,3 @@ * @param {String} fileName source filename

*/
MailParser.prototype.generateFileName = function(fileName){
MailParser.prototype._generateFileName = function(fileName){
var ext;

@@ -938,14 +980,38 @@

if(fileName in this.fileNames){
this.fileNames[fileName]++;
if(fileName in this._fileNames){
this._fileNames[fileName]++;
ext = fileName.substr((fileName.lastIndexOf(".") || 0)+1);
if(ext == fileName){
fileName += "-" + this.fileNames[fileName];
fileName += "-" + this._fileNames[fileName];
}else{
fileName = fileName.substr(0, fileName.length - ext.length - 1) + "-" + this.fileNames[fileName] + "." + ext;
fileName = fileName.substr(0, fileName.length - ext.length - 1) + "-" + this._fileNames[fileName] + "." + ext;
}
}else{
this.fileNames[fileName] = 0;
this._fileNames[fileName] = 0;
}
return fileName;
};
/**
* <p>Replaces character set to UTF-8 in HTML &lt;meta&gt; tags</p>
*
* @param {String} HTML html contents
* @returns {String} updated HTML
*/
MailParser.prototype._updateHTMLCharset = function(html){
html = html.replace(/\n/g,"\u0000").
replace(/<meta[^>]*>/gi, function(meta){
if(meta.match(/http\-equiv\s*=\s*"?content\-type/i)){
return '<meta http-equiv="content-type" content="text/html; charset=utf-8" />';
}
if(meta.match(/\scharset\s*=\s*['"]?[\w\-]+["'\s>\/]/i)){
return '<meta charset="utf-8"/>'
}
return meta;
}).
replace(/\u0000/g,"\n");
return html;
};
{
"name": "mailparser",
"description": "Asynchronous and non-blocking parser for mime encoded e-mail messages",
"version": "0.2.4",
"version": "0.2.6",
"author" : "Andris Reinman",

@@ -24,3 +24,3 @@ "maintainers":[

"dependencies": {
"mimelib": "0.1.8",
"mimelib": "0.1.10",
"iconv": "*"

@@ -27,0 +27,0 @@ },

MailParser

@@ -8,4 +7,3 @@ ==========

not upgrade from 0.1.x without updating your code, the API is totally
different. Also if the source is coming directly from SMTP you need to
unescape dots in the beginning of the lines yourself!
different.

@@ -27,2 +25,7 @@

Live Demo
---------
You can test this module in action here: http://node.ee/MailParser/Demo
Installation

@@ -42,4 +45,10 @@ ------------

var mailparser = new MailParser();
var mailparser = new MailParser([options]);
Options parameter is an object with the following properties:
* **debug** - if set to true print all incoming lines to console
* **streamAttachments** - if set to true, stream attachments instead of including them
* **unescapeSMTP** - if set to true replace double dots in the beginning of the file
MailParser object is a writable Stream - you can pipe directly

@@ -134,11 +143,6 @@ files to it or you can send chunks with `mailparser.write`

The property `generatedFileName` is usually the same but if several different
attachments with the same name exist, the latter ones names will be modified and
the generated name will be saved to `generatedFileName`
The property `generatedFileName` is usually the same as `fileName` but if several
different attachments with the same name exist or there is no `fileName` set, an
unique name is generated.
file.txt
file-1.txt
file-2.txt
...
Property `content` is always a Buffer object (or SlowBuffer on some occasions)

@@ -145,0 +149,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc