Comparing version 1.0.0 to 1.0.1
@@ -86,3 +86,2 @@ 'use strict'; | ||
// pipe message from file to splitter | ||
fs.createReadStream(__dirname + '/message.eml').pipe(splitter); |
@@ -37,2 +37,27 @@ 'use strict'; | ||
let checkTrailingLinebreak = data => { | ||
if (data.type === 'body' && data.node.parentNode && data.value && data.value.length) { | ||
if (data.value[data.value.length - 1] === 0x0A) { | ||
groupstart--; | ||
pos--; | ||
if (data.value.length > 1 && data.value[data.value.length - 2] === 0x0D) { | ||
groupstart--; | ||
pos--; | ||
if (groupstart < 0 && !this.line) { | ||
// store only <CR> as <LF> should be on the positive side | ||
this.line = Buffer.allocUnsafe(1); | ||
this.line[0] = 0x0D; | ||
} | ||
data.value = data.value.slice(0, data.value.length - 2); | ||
} else { | ||
data.value = data.value.slice(0, data.value.length - 1); | ||
} | ||
} else if (data.value[data.value.length - 1] === 0x0D) { | ||
groupstart--; | ||
pos--; | ||
data.value = data.value.slice(0, data.value.length - 1); | ||
} | ||
} | ||
}; | ||
let iterateData = () => { | ||
@@ -56,3 +81,3 @@ for (let len = chunk.length; i < len; i++) { | ||
if (group.type === 'body' && groupend > groupstart) { | ||
if (group.type === 'body' && groupend >= groupstart && group.node.parentNode) { | ||
// do not include the last line ending for body | ||
@@ -72,10 +97,19 @@ if (chunk[groupend - 1] === 0x0A) { | ||
group.value = chunk.slice(groupstart, groupend); | ||
this.push(group); | ||
if (group.value && group.value.length) { | ||
this.push(group); | ||
} | ||
} | ||
} | ||
if (data.type === 'node' || groupstart < 0) { | ||
if (data.type === 'node') { | ||
this.push(data); | ||
groupstart = i; | ||
groupend = i; | ||
} else if (groupstart < 0) { | ||
groupstart = i; | ||
groupend = i; | ||
checkTrailingLinebreak(data); | ||
if (data.value && data.value.length) { | ||
this.push(data); | ||
} | ||
} else { | ||
@@ -95,3 +129,3 @@ // start new body/data chunk | ||
// skip last linebreak for body | ||
if (pos >= groupstart + 1 && group.type === 'body') { | ||
if (pos >= groupstart + 1 && group.type === 'body' && group.node.parentNode) { | ||
// do not include the last line ending for body | ||
@@ -109,3 +143,5 @@ if (chunk[pos - 1] === 0x0A) { | ||
group.value = chunk.slice(groupstart, pos); | ||
this.push(group); | ||
if (group.value && group.value.length) { | ||
this.push(group); | ||
} | ||
} | ||
@@ -128,3 +164,3 @@ | ||
this.processLine(false, true, data => { | ||
if (data) { | ||
if (data && (data.type === 'node' || (data.value && data.value.length))) { | ||
this.push(data); | ||
@@ -255,2 +291,3 @@ } | ||
next({ | ||
node: this.node, | ||
type: 'data', | ||
@@ -261,2 +298,3 @@ value: line | ||
next({ | ||
node: this.node, | ||
type: 'body', | ||
@@ -300,2 +338,3 @@ value: line | ||
return next({ | ||
node: this.node, | ||
type: 'data', | ||
@@ -302,0 +341,0 @@ value: line |
@@ -38,2 +38,3 @@ 'use strict'; | ||
this.contentType = (this._parsedContentType.value || '').toLowerCase().trim(); | ||
this.charset = this._parsedContentType.params.charset || false; | ||
@@ -40,0 +41,0 @@ this._multipart = this.contentType.substr(0, this.contentType.indexOf('/')) === 'multipart' && this.contentType.substr(this.contentType.indexOf('/') + 1) || false; |
{ | ||
"name": "mailsplit", | ||
"version": "1.0.0", | ||
"version": "1.0.1", | ||
"description": "Split email messages into an object stream", | ||
@@ -22,4 +22,6 @@ "main": "index.js", | ||
"grunt-eslint": "^19.0.0", | ||
"libbase64": "^0.1.0", | ||
"libqp": "^1.1.0", | ||
"random-message": "^1.1.0" | ||
} | ||
} |
@@ -9,4 +9,14 @@ # mailsplit | ||
Supports both <CR><LF> and <LF> (even 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 is for the container node and second for the root node of the embedded message. | ||
Supports both <CR><LF> and <LF> (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). | ||
## Data objects | ||
* **type** | ||
* `'node'` means that we entered into next mime node and the previous one is now 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 | ||
* **header** is an object for manipulating headers (see below) | ||
* **getHeaders()** is a convenience method for returning all headers (including the terminating empty line) as a single buffer value. If you have modified the headers then these modifications are also included in the output | ||
## Usage | ||
@@ -45,2 +55,19 @@ | ||
### Manipulating headers | ||
If the data object has `type='node'` then it also has a `headers` object property. This object has several methods for manipulating header values | ||
* **node.headers.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 <CR><LF> even if the original headers used just <LF> | ||
* **node.headers.getFirst(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) | ||
* **node.headers.get(key)** returns string value of the specified header key | ||
* **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 | ||
Additionally you can check the details of the node with the following properties automatically parsed from the headers: | ||
* **node.contentType** returns the mime type of the node (eg. 'text/html') | ||
* **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 | ||
### Join parsed message stream | ||
@@ -47,0 +74,0 @@ |
@@ -37,2 +37,32 @@ 'use strict'; | ||
module.exports['Split simple message with line ending'] = test => { | ||
let splitter = new MessageSplitter(); | ||
let tests = [ | ||
data => { | ||
test.equal(data.type, 'node'); | ||
test.equal(data.getHeaders().toString(), 'Subject: test\nMime-Version: 1.0\n\n'); | ||
}, | ||
data => { | ||
test.equal(data.type, 'body'); | ||
test.equal(data.value.toString(), 'Hello world!\r\n'); | ||
} | ||
]; | ||
test.expect(6); | ||
splitter.on('data', data => { | ||
let nextTest = tests.shift(); | ||
test.ok(nextTest); | ||
nextTest(data); | ||
}); | ||
splitter.on('end', () => { | ||
test.done(); | ||
}); | ||
splitter.end('Subject: test\nMime-Version: 1.0\n\nHello world!\r\n'); | ||
}; | ||
module.exports['Split message with header only 1'] = test => { | ||
@@ -96,9 +126,5 @@ | ||
test.equal(data.getHeaders().toString(), 'Subject: test\nMime-Version: 1.0\n\n'); | ||
}, | ||
data => { | ||
test.equal(data.type, 'body'); | ||
test.equal(data.value.toString(), ''); | ||
} | ||
]; | ||
test.expect(6); | ||
test.expect(3); | ||
@@ -105,0 +131,0 @@ splitter.on('data', data => { |
194930
1056
98
7