New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

mailsplit

Package Overview
Dependencies
Maintainers
1
Versions
53
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mailsplit - npm Package Compare versions

Comparing version 4.0.2 to 4.1.0

9

lib/headers.js

@@ -39,3 +39,5 @@ 'use strict';

getDecoded(key) {
return this.get(key).map(line => libmime.decodeHeader(line)).filter(line => line && line.value);
return this.get(key)
.map(line => libmime.decodeHeader(line))
.filter(line => line && line.value);
}

@@ -158,3 +160,6 @@

let lines = this.headers.toString('binary').replace(/[\r\n]+$/, '').split(/\r?\n/);
let lines = this.headers
.toString('binary')
.replace(/[\r\n]+$/, '')
.split(/\r?\n/);

@@ -161,0 +166,0 @@ for (let i = lines.length - 1; i >= 0; i--) {

@@ -24,2 +24,3 @@ 'use strict';

this.line = false;
this.errored = false;
}

@@ -40,7 +41,7 @@

if (data.type === 'body' && data.node.parentNode && data.value && data.value.length) {
if (data.value[data.value.length - 1] === 0x0A) {
if (data.value[data.value.length - 1] === 0x0a) {
groupstart--;
groupend--;
pos--;
if (data.value.length > 1 && data.value[data.value.length - 2] === 0x0D) {
if (data.value.length > 1 && data.value[data.value.length - 2] === 0x0d) {
groupstart--;

@@ -52,3 +53,3 @@ groupend--;

this.line = Buffer.allocUnsafe(1);
this.line[0] = 0x0D;
this.line[0] = 0x0d;
}

@@ -59,3 +60,3 @@ data.value = data.value.slice(0, data.value.length - 2);

}
} else if (data.value[data.value.length - 1] === 0x0D) {
} else if (data.value[data.value.length - 1] === 0x0d) {
groupstart--;

@@ -72,3 +73,4 @@ groupend--;

// find next <LF>
if (chunk[i] === 0x0A) { // line end
if (chunk[i] === 0x0a) {
// line end

@@ -80,2 +82,3 @@ let start = Math.max(pos, 0);

if (err) {
this.errored = true;
return setImmediate(() => callback(err));

@@ -92,5 +95,5 @@ }

// do not include the last line ending for body
if (chunk[groupend - 1] === 0x0A) {
if (chunk[groupend - 1] === 0x0a) {
groupend--;
if (groupend >= groupstart && chunk[groupend - 1] === 0x0D) {
if (groupend >= groupstart && chunk[groupend - 1] === 0x0d) {
groupend--;

@@ -123,5 +126,5 @@ }

// do not include the last line ending for body
if (chunk[groupend - 1] === 0x0A) {
if (chunk[groupend - 1] === 0x0a) {
groupend--;
if (groupend >= groupstart && chunk[groupend - 1] === 0x0D) {
if (groupend >= groupstart && chunk[groupend - 1] === 0x0d) {
groupend--;

@@ -171,5 +174,5 @@ }

// do not include the last line ending for body
if (chunk[pos - 1] === 0x0A) {
if (chunk[pos - 1] === 0x0a) {
pos--;
if (pos >= groupstart && chunk[pos - 1] === 0x0D) {
if (pos >= groupstart && chunk[pos - 1] === 0x0d) {
pos--;

@@ -202,6 +205,9 @@ }

iterateData();
setImmediate(iterateData);
}
_flush(callback) {
if (this.errored) {
return callback();
}
this.processLine(false, true, (err, data) => {

@@ -232,19 +238,19 @@ if (err) {

let c = line[i];
if (pos === 0 && (c === 0x0D || c === 0x0A)) {
if (pos === 0 && (c === 0x0d || c === 0x0a)) {
// 1: next node
return 1;
}
if (pos === 0 && c !== 0x2D) {
if (pos === 0 && c !== 0x2d) {
// expecting "-"
return false;
}
if (pos === 1 && c !== 0x2D) {
if (pos === 1 && c !== 0x2d) {
// expecting "-"
return false;
}
if (pos === 2 && c !== 0x0D && c !== 0x0A) {
if (pos === 2 && c !== 0x0d && c !== 0x0a) {
// expecting line terminator, either <CR> or <LF>
return false;
}
if (pos === 3 && c !== 0x0A) {
if (pos === 3 && c !== 0x0a) {
// expecting line terminator <LF>

@@ -262,9 +268,9 @@ return false;

let startpos = 0;
if (line.length >= 1 && (line[0] === 0x0D || line[0] === 0x0A)) {
if (line.length >= 1 && (line[0] === 0x0d || line[0] === 0x0a)) {
startpos++;
if (line.length >= 2 && (line[0] === 0x0D || line[1] === 0x0A)) {
if (line.length >= 2 && (line[0] === 0x0d || line[1] === 0x0a)) {
startpos++;
}
}
if (line.length < 4 || line[startpos] !== 0x2D || line[startpos + 1] !== 0x2D) {
if (line.length < 4 || line[startpos] !== 0x2d || line[startpos + 1] !== 0x2d) {
// defnitely not a boundary

@@ -306,99 +312,113 @@ return false;

switch (this.state) {
case HEAD:
{
this.node.addHeaderChunk(line);
if (this.node._headerlen > this.maxHeadSize) {
let err = new Error('Max header size for a MIME node exceeded');
err.code = 'EMAXLEN';
return next(err);
}
if (final || (line.length === 1 && line[0] === 0x0A) || (line.length === 2 && line[0] === 0x0D && line[1] === 0x0A)) {
let currentNode = this.node;
case HEAD: {
this.node.addHeaderChunk(line);
if (this.node._headerlen > this.maxHeadSize) {
let err = new Error('Max header size for a MIME node exceeded');
err.code = 'EMAXLEN';
return next(err);
}
if (final || (line.length === 1 && line[0] === 0x0a) || (line.length === 2 && line[0] === 0x0d && line[1] === 0x0a)) {
let currentNode = this.node;
currentNode.parseHeaders();
currentNode.parseHeaders();
// if the content is attached message then just continue
if (currentNode.contentType === 'message/rfc822' && !this.config.ignoreEmbedded && (!currentNode.encoding || ['7bit', '8bit', 'binary'].includes(currentNode.encoding)) && currentNode.disposition !== 'attachment') {
currentNode.messageNode = true;
this.state = HEAD;
this.node = new MimeNode(currentNode);
if (currentNode.parentNode) {
this.node._parentBoundary = currentNode.parentNode._boundary;
}
} else {
if (currentNode.contentType === 'message/rfc822') {
currentNode.messageNode = false;
}
this.state = BODY;
if (currentNode.multipart && currentNode._boundary) {
this.tree.push(currentNode);
}
// if the content is attached message then just continue
if (
currentNode.contentType === 'message/rfc822' &&
!this.config.ignoreEmbedded &&
(!currentNode.encoding || ['7bit', '8bit', 'binary'].includes(currentNode.encoding)) &&
currentNode.disposition !== 'attachment'
) {
currentNode.messageNode = true;
this.state = HEAD;
this.node = new MimeNode(currentNode);
if (currentNode.parentNode) {
this.node._parentBoundary = currentNode.parentNode._boundary;
}
return next(null, currentNode, flush);
} else {
if (currentNode.contentType === 'message/rfc822') {
currentNode.messageNode = false;
}
this.state = BODY;
if (currentNode.multipart && currentNode._boundary) {
this.tree.push(currentNode);
}
}
return next();
return next(null, currentNode, flush);
}
case BODY:
{
let boundary = this.checkBoundary(line);
if (!boundary) {
// not a boundary line
if (this.node.multipart) {
next(null, {
return next();
}
case BODY: {
let boundary = this.checkBoundary(line);
if (!boundary) {
// not a boundary line
if (this.node.multipart) {
next(
null,
{
node: this.node,
type: 'data',
value: line
}, flush);
} else {
next(null, {
},
flush
);
} else {
next(
null,
{
node: this.node,
type: 'body',
value: line
}, flush);
}
return;
},
flush
);
}
return;
}
// reached boundary. switch context
switch (boundary) {
case 1:
// next child
this.node = new MimeNode(this.node);
this.state = HEAD;
flush = true;
break;
case 2:
// reached end of children, keep current node
break;
case 3:
{
// next sibling
let parentNode = this.node.parentNode;
if (parentNode && parentNode.contentType === 'message/rfc822') {
// special case where immediate parent is an inline message block
// move up another step
parentNode = parentNode.parentNode;
}
this.node = new MimeNode(parentNode);
this.state = HEAD;
flush = true;
break;
}
case 4:
// move up
if (this.tree.length) {
this.node = this.tree.pop();
}
this.state = BODY;
break;
// reached boundary. switch context
switch (boundary) {
case 1:
// next child
this.node = new MimeNode(this.node);
this.state = HEAD;
flush = true;
break;
case 2:
// reached end of children, keep current node
break;
case 3: {
// next sibling
let parentNode = this.node.parentNode;
if (parentNode && parentNode.contentType === 'message/rfc822') {
// special case where immediate parent is an inline message block
// move up another step
parentNode = parentNode.parentNode;
}
this.node = new MimeNode(parentNode);
this.state = HEAD;
flush = true;
break;
}
case 4:
// move up
if (this.tree.length) {
this.node = this.tree.pop();
}
this.state = BODY;
break;
}
return next(null, {
return next(
null,
{
node: this.node,
type: 'data',
value: line
}, flush);
}
},
flush
);
}
}

@@ -405,0 +425,0 @@

@@ -46,3 +46,7 @@ 'use strict';

this.encoding = this.headers.getFirst('Content-Transfer-Encoding').replace(/\(.*\)/g, '').toLowerCase().trim();
this.encoding = this.headers
.getFirst('Content-Transfer-Encoding')
.replace(/\(.*\)/g, '')
.toLowerCase()
.trim();
this.contentType = (this._parsedContentType.value || '').toLowerCase().trim() || false;

@@ -68,4 +72,8 @@ this.charset = this._parsedContentType.params.charset || false;

this.multipart = this.contentType && this.contentType.substr(0, this.contentType.indexOf('/')) === 'multipart' && this.contentType.substr(this.contentType.indexOf('/') + 1) || false;
this._boundary = this._parsedContentType.params.boundary && Buffer.from(this._parsedContentType.params.boundary) || false;
this.multipart =
(this.contentType &&
this.contentType.substr(0, this.contentType.indexOf('/')) === 'multipart' &&
this.contentType.substr(this.contentType.indexOf('/') + 1)) ||
false;
this._boundary = (this._parsedContentType.params.boundary && Buffer.from(this._parsedContentType.params.boundary)) || false;
}

@@ -178,3 +186,6 @@

encoding = (encoding || '').toString().toLowerCase().trim();
encoding = (encoding || '')
.toString()
.toLowerCase()
.trim();

@@ -181,0 +192,0 @@ if (encoding && encoding !== this.encoding) {

@@ -38,5 +38,8 @@ 'use strict';

// emit an empty node just in case there is pending data to end
return this.processIncoming({
type: 'none'
}, callback);
return this.processIncoming(
{
type: 'none'
},
callback
);
}

@@ -43,0 +46,0 @@ return callback();

@@ -38,5 +38,8 @@ 'use strict';

// emit an empty node just in case there is pending data to end
return this.processIncoming({
type: 'none'
}, callback);
return this.processIncoming(
{
type: 'none'
},
callback
);
}

@@ -43,0 +46,0 @@ return callback();

{
"name": "mailsplit",
"version": "4.0.2",
"version": "4.1.0",
"description": "Split email messages into an object stream",

@@ -16,10 +16,11 @@ "main": "index.js",

"libmime": "3.1.0",
"libbase64": "0.1.0",
"libbase64": "1.0.1",
"libqp": "1.1.0"
},
"devDependencies": {
"eslint-config-nodemailer": "^1.2.0",
"grunt": "^1.0.1",
"grunt-cli": "^1.2.0",
"grunt-contrib-nodeunit": "^1.0.0",
"grunt-eslint": "^19.0.0",
"grunt-eslint": "^20.1.0",
"random-message": "^1.1.0"

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

@@ -7,10 +7,8 @@ # mailsplit

Supports both &lt;CR&gt;&lt;LF&gt; and &lt;LF&gt; (or mixed) line endings. Embedded rfc822 messages are also parsed, in this case you would get two sequential 'node' objects with no 'data' or 'body' in between (first 'node' is for the container node and second for the root node of the embedded message).
Supports both &lt;CR&gt;&lt;LF&gt; and &lt;LF&gt; (or mixed) line endings. Embedded rfc822 messages are also parsed, in this case you would get two sequential 'node' objects with no 'data' or 'body' in between (first 'node' is for the container node and second for the root node of the embedded message).
In general this module is a primitive for building e-mail parsers/handlers like [mailparser](https://www.npmjs.com/package/mailparser). Alternatively you could use it to parse other MIME-like structures, for example *mbox* files or multipart/form-data uploads.
In general this module is a primitive for building e-mail parsers/handlers like [mailparser](https://www.npmjs.com/package/mailparser). Alternatively you could use it to parse other MIME-like structures, for example _mbox_ files or multipart/form-data uploads.
See [rewrite-html.js](examples/rewrite-html.js) for an usage example where HTML content is modified on the fly (example script adds a link to every *text/html* node)
See [rewrite-html.js](examples/rewrite-html.js) for an usage example where HTML content is modified on the fly (example script adds a link to every _text/html_ node)
> This module is part of the [Nodemailer bundle](https://nodemailer.com/about/pricing/). Starting from v4.0.0 *mailsplit* is licensed under the [European Union Public License 1.1](http://ec.europa.eu/idabc/eupl.html). In general, EUPLv1.1 is a _copyleft_ license compatible with GPLv2, so if you're OK using GPL then you should be OK using *mailsplit*. Previous versions of *mailsplit* are licensed under the MIT license.
## Usage

@@ -35,5 +33,5 @@

* **options** is an optional options object
* **options.ignoreEmbedded** (boolean, defaults to false) if true then treat message/rfc822 node as normal leaf node and do not try to parse it
* **options.maxHeadSize** (number, defaults to Infinity) limits message header size in bytes
* **options** is an optional options object
* **options.ignoreEmbedded** (boolean, defaults to false) if true then treat message/rfc822 node as normal leaf node and do not try to parse it
* **options.maxHeadSize** (number, defaults to Infinity) limits message header size in bytes

@@ -46,9 +44,9 @@ #### Events

* **type**
* `'node'` means that we reached the next mime node and the previous one is completely processed
* `'data'` provides us multipart body parts, including boundaries. This data is not directly related to any specific multipart node, basically it includes everything between the end of one normal node and the header of next node
* `'body'` provides us next chunk for the last seen `'node'` element
* **value** is a buffer value for `'body'` and `'data'` parts
* **getDecoder()** is a function that returns a stream object you can use to decode node contents. Write data from 'body' to decoder and read decoded Buffer value out from it
* **getEncoder()** is a function that returns a stream object you can use to encode node contents. Write buffer data to encoder and read encoded object value out that you can pass to a Joiner
* **type**
* `'node'` means that we reached the next mime node and the previous one is completely processed
* `'data'` provides us multipart body parts, including boundaries. This data is not directly related to any specific multipart node, basically it includes everything between the end of one normal node and the header of next node
* `'body'` provides us next chunk for the last seen `'node'` element
* **value** is a buffer value for `'body'` and `'data'` parts
* **getDecoder()** is a function that returns a stream object you can use to decode node contents. Write data from 'body' to decoder and read decoded Buffer value out from it
* **getEncoder()** is a function that returns a stream object you can use to encode node contents. Write buffer data to encoder and read encoded object value out that you can pass to a Joiner

@@ -63,4 +61,4 @@ Element with type `'node'` has a bunch of header related methods and properties, see [below](#manipulating-headers).

// handle parsed data
splitter.on('data', (data)=>{
switch(data.type){
splitter.on('data', data => {
switch (data.type) {
case 'node':

@@ -74,3 +72,3 @@ // node header block

// everything between the end of some node body and between the next header
process.stdout.write(data.value)
process.stdout.write(data.value);
break;

@@ -80,3 +78,3 @@ case 'body':

// have several 'body' calls for a single 'node' block
process.stdout.write(data.value)
process.stdout.write(data.value);
break;

@@ -93,26 +91,26 @@ }

* **node.getHeaders()** returns a Buffer value with generated headers. If you have not modified the headers object in any way then you should get the exact copy of the original. In case you have done something (for example removed a key, or added a new header key), then all linebreaks are forced to &lt;CR&gt;&lt;LF&gt; even if the original headers used just &lt;LF&gt;
* **node.setContentType(contentType)** sets or updates mime type for the node
* **node.setCharset(charset)** sets or updates character set in the Content-Type header
* **node.setFilename(filename)** sets or updates filename in the Content-Disposition header (unicode allowed)
* **node.getHeaders()** returns a Buffer value with generated headers. If you have not modified the headers object in any way then you should get the exact copy of the original. In case you have done something (for example removed a key, or added a new header key), then all linebreaks are forced to &lt;CR&gt;&lt;LF&gt; even if the original headers used just &lt;LF&gt;
* **node.setContentType(contentType)** sets or updates mime type for the node
* **node.setCharset(charset)** sets or updates character set in the Content-Type header
* **node.setFilename(filename)** sets or updates filename in the Content-Disposition header (unicode allowed)
You can manipulate specific header keys as well using the `headers` object
* **node.headers.get(key)** returns an array of strings with all header rows for the selected key (these are full header lines, so key name is part of the row string, eg `["Subject: This is subject line"]`)
* **node.headers.getFirst(key)** returns string value of the specified header key (eg `"This is subject line"`)
* **node.headers.add(key, value [,index])** adds a new header value to the specified index or to the top of the header block if index is not specified
* **node.headers.update(key, value)** replaces a header value for the specified key
* **node.headers.delete(key)** remove header value
* **node.headers.mbox** If this is a MBOX formatted message then this value holds the prefix line (eg. "From MAILER-DAEMON Fri Jul 8 12:08:34 2011")
* **node.headers.mbox** If this is a POST form-data then this value holds the HTTP prefix line (eg. "POST /upload.php HTTP/1.1")
* **node.headers.get(key)** returns an array of strings with all header rows for the selected key (these are full header lines, so key name is part of the row string, eg `["Subject: This is subject line"]`)
* **node.headers.getFirst(key)** returns string value of the specified header key (eg `"This is subject line"`)
* **node.headers.add(key, value [,index])** adds a new header value to the specified index or to the top of the header block if index is not specified
* **node.headers.update(key, value)** replaces a header value for the specified key
* **node.headers.delete(key)** remove header value
* **node.headers.mbox** If this is a MBOX formatted message then this value holds the prefix line (eg. "From MAILER-DAEMON Fri Jul 8 12:08:34 2011")
* **node.headers.mbox** If this is a POST form-data then this value holds the HTTP prefix line (eg. "POST /upload.php HTTP/1.1")
Additionally you can check the details of the node with the following properties automatically parsed from the headers:
* **node.root** if true then it means this is the message root, so this node should contain Subject, From, To etc. headers
* **node.contentType** returns the mime type of the node (eg. 'text/html')
* **node.disposition** either `'attachment'`, `'inline'` or `false` if not set
* **node.charset** returns the charset of the node as defined in 'Content-Type' header (eg. 'UTF-8') or false if not defined
* **node.encoding** returns the Transfer-Encoding value (eg. 'base64' or 'quoted-printable') or false if not defined
* **node.multipart** if has value, then this is a multipart node (does not have 'body' parts)
* **node.filename** is set if the headers contain a filename value. This is decoded to unicode, so it is a normal string or false if not found
* **node.root** if true then it means this is the message root, so this node should contain Subject, From, To etc. headers
* **node.contentType** returns the mime type of the node (eg. 'text/html')
* **node.disposition** either `'attachment'`, `'inline'` or `false` if not set
* **node.charset** returns the charset of the node as defined in 'Content-Type' header (eg. 'UTF-8') or false if not defined
* **node.encoding** returns the Transfer-Encoding value (eg. 'base64' or 'quoted-printable') or false if not defined
* **node.multipart** if has value, then this is a multipart node (does not have 'body' parts)
* **node.filename** is set if the headers contain a filename value. This is decoded to unicode, so it is a normal string or false if not found

@@ -129,3 +127,6 @@ ### Join parsed message stream

// pipe a message source to splitter, then joiner and finally to stdout
someMessagStream.pipe(splitter).pipe(joiner).pipe(process.stdout);
someMessagStream
.pipe(splitter)
.pipe(joiner)
.pipe(process.stdout);
```

@@ -139,10 +140,10 @@

* **filterFunc** gets the current node as argument and starts processing it if `filterFunc` returns true
* **filterFunc** gets the current node as argument and starts processing it if `filterFunc` returns true
Once Rewriter finds a matching node, it emits the following event:
* *'node'* with an object argument `data`
* `data.node` includes the current node with headers
* `data.decoder` is the decoder stream that you can read data from
* `data.encoder` is the encoder stream that you can write data to. Whatever you write into that stream will be encoded properly and inserted as the content of the current node
* _'node'_ with an object argument `data`
* `data.node` includes the current node with headers
* `data.decoder` is the decoder stream that you can read data from
* `data.encoder` is the encoder stream that you can write data to. Whatever you write into that stream will be encoded properly and inserted as the content of the current node

@@ -155,3 +156,3 @@ ```javascript

let joiner = new Joiner();
let rewriter = new Rewriter(node=>node.contentType === 'text/html');
let rewriter = new Rewriter(node => node.contentType === 'text/html');
rewriter.on('node', data => {

@@ -164,3 +165,7 @@ // manage headers with node.headers

// pipe a message source to splitter, then rewriter, then joiner and finally to stdout
someMessagStream.pipe(splitter).pipe(rewriter).pipe(joiner).pipe(process.stdout);
someMessagStream
.pipe(splitter)
.pipe(rewriter)
.pipe(joiner)
.pipe(process.stdout);
```

@@ -174,10 +179,10 @@

* **filterFunc** gets the current node as argument and starts processing it if `filterFunc` returns true
* **filterFunc** gets the current node as argument and starts processing it if `filterFunc` returns true
Once Streamer finds a matching node, it emits the following event:
* *'node'* with an object argument `data`
* `data.node` includes the current node with headers (informational only, you can't modify it)
* `data.decoder` is the decoder stream that you can read data from
* `data.done` is a function you must call once you have processed the stream
* _'node'_ with an object argument `data`
* `data.node` includes the current node with headers (informational only, you can't modify it)
* `data.decoder` is the decoder stream that you can read data from
* `data.done` is a function you must call once you have processed the stream

@@ -191,3 +196,3 @@ ```javascript

let joiner = new Joiner();
let streamer = new Streamer(node=>node.contentType === 'image/jpeg');
let streamer = new Streamer(node => node.contentType === 'image/jpeg');
streamer.on('node', data => {

@@ -199,3 +204,7 @@ // write to file

// pipe a message source to splitter, then streamer, then joiner and finally to stdout
someMessagStream.pipe(splitter).pipe(streamer).pipe(joiner).pipe(process.stdout);
someMessagStream
.pipe(splitter)
.pipe(streamer)
.pipe(joiner)
.pipe(process.stdout);
```

@@ -202,0 +211,0 @@

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