Comparing version 0.1.0 to 0.1.1
{ | ||
"name": "libmime", | ||
"description": "Encode and decode quoted printable and base64 strings", | ||
"version": "0.1.0", | ||
"main": "src/libmime", | ||
"homepage": "https://github.com/andris9/libmime", | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/andris9/libmime.git" | ||
}, | ||
"license": "MIT", | ||
"keywords": [ | ||
"MIME", | ||
"Base64", | ||
"Quoted-Printable" | ||
], | ||
"author": "Andris Reinman <andris@kreata.ee>", | ||
"scripts": { | ||
"test": "grunt" | ||
}, | ||
"dependencies": { | ||
"iconv-lite": "^0.4.0" | ||
}, | ||
"devDependencies": { | ||
"chai": "~1.8.1", | ||
"grunt": "~0.4.1", | ||
"grunt-contrib-jshint": "~0.8.0", | ||
"grunt-mocha-test": "~0.10.0" | ||
} | ||
} | ||
"name": "libmime", | ||
"description": "Encode and decode quoted printable and base64 strings", | ||
"version": "0.1.1", | ||
"main": "src/libmime", | ||
"homepage": "https://github.com/andris9/libmime", | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/andris9/libmime.git" | ||
}, | ||
"license": "MIT", | ||
"keywords": [ | ||
"MIME", | ||
"Base64", | ||
"Quoted-Printable" | ||
], | ||
"author": "Andris Reinman <andris@kreata.ee>", | ||
"scripts": { | ||
"test": "grunt" | ||
}, | ||
"dependencies": { | ||
"iconv-lite": "^0.4.0", | ||
"libbase64": "^0.1.0", | ||
"libqp": "^0.1.1" | ||
}, | ||
"devDependencies": { | ||
"chai": "~1.8.1", | ||
"grunt": "~0.4.1", | ||
"grunt-contrib-jshint": "~0.8.0", | ||
"grunt-mocha-test": "~0.10.0" | ||
} | ||
} |
132
README.md
# libmime | ||
`libmime` provides useful MIME related functions like encoding and decoding quoted-printable strings or detecting content-type strings for file extensions. | ||
`libmime` provides useful MIME related functions. For Quoted-Printable and Base64 encoding and decoding see [libqp](https://github.com/andris9/libqp) and [libbase64](https://github.com/andris9/libabase64). | ||
@@ -17,49 +17,9 @@ ## Installation | ||
### Quoted Printable encoding | ||
#### #quotedPrintableEncode | ||
Encodes a string into Quoted-printable format. | ||
libmime.quotedPrintableEncode(str [, fromCharset]) -> String | ||
* **str** - String or an Buffer to mime encode | ||
* **fromCharset** - If the first parameter is a Buffer object, use this charset to decode the value to unicode before encoding | ||
#### #quotedPrintableDecode | ||
Decodes a string from Quoted-printable format. | ||
libmime.quotedPrintableDecode(str [, fromCharset]) -> String | ||
* **str** - Mime encoded string | ||
* **fromCharset** - Use this charset to decode mime encoded string to unicode | ||
### Base64 Encoding | ||
#### #base64Encode | ||
Encodes a string into Base64 format. | ||
libmime.base64Encode(str [, fromCharset]) -> String | ||
* **str** - String or an Buffer to base64 encode | ||
* **fromCharset** - If the first parameter is a Buffer object, use this charset to decode the value to unicode before encoding | ||
#### #base64Decode | ||
Decodes a string from Base64 format to an unencoded unicode string. | ||
libmime.base64Decode(str [, fromCharset]) -> String | ||
* **str** Base64 encoded string | ||
* **fromCharset** Use this charset to decode base64 encoded string to unicode | ||
### Encoded Words | ||
#### #mimeWordEncode | ||
#### #encodeWord | ||
Encodes a string into mime [encoded word](http://en.wikipedia.org/wiki/MIME#Encoded-Word) format. | ||
libmime.mimeWordEncode(str [, mimeWordEncoding[, maxLength[, fromCharset]]]) -> String | ||
libmime.encodeWord(str [, mimeWordEncoding[, maxLength]]) → String | ||
@@ -69,7 +29,6 @@ * **str** - String or Buffer to be encoded | ||
* **maxLength** - If set, split mime words into several chunks if needed | ||
* **fromCharset** - If the first parameter is a Buffer object, use this encoding to decode the value to unicode | ||
**Example** | ||
libmime.mimeWordEncode('See on õhin test', 'Q'); | ||
libmime.encodeWord('See on õhin test', 'Q'); | ||
@@ -80,7 +39,7 @@ Becomes with UTF-8 and Quoted-printable encoding | ||
#### #mimeWordDecode | ||
#### #decodeWord | ||
Decodes a string from mime encoded word format. | ||
libmime.mimeWordDecode(str) -> String | ||
libmime.decodeWord(str) → String | ||
@@ -91,3 +50,3 @@ * **str** - String to be decoded | ||
libmime.mimeWordDecode('=?UTF-8?Q?See_on_=C3=B5hin_test?='); | ||
libmime.decodeWord('=?UTF-8?Q?See_on_=C3=B5hin_test?='); | ||
@@ -98,7 +57,7 @@ will become | ||
#### #mimeWordsEncode | ||
#### #encodeWords | ||
Encodes non ascii sequences in a string to mime words. | ||
libmime.mimeWordsEncode(str[, mimeWordEncoding[, maxLength[, fromCharset]]]) -> String | ||
libmime.encodeWords(str[, mimeWordEncoding[, maxLength]) → String | ||
@@ -108,9 +67,8 @@ * **str** - String or Buffer to be encoded | ||
* **maxLength** - If set, split mime words into several chunks if needed | ||
* **fromCharset** - If the first parameter is a Buffer object, use this charset to decode the value to unicode before encoding | ||
#### #mimeWordsDecode | ||
#### #decodeWords | ||
Decodes a string that might include one or several mime words. If no mime words are found from the string, the original string is returned | ||
libmime.mimeWordsDecode(str) -> String | ||
libmime.decodeWords(str) → String | ||
@@ -125,6 +83,6 @@ * **str** - String to be decoded | ||
libmime.foldLines(str [, lineLengthMax[, afterSpace]]) -> String | ||
libmime.foldLines(str [, lineLength[, afterSpace]]) → String | ||
* **str** - String to be folded | ||
* **lineLengthMax** - Maximum length of a line (defaults to 76) | ||
* **lineLength** - Maximum length of a line (defaults to 76) | ||
* **afterSpace** - If true, leave a space in the end of a line | ||
@@ -141,17 +99,17 @@ | ||
#### #addSoftLinebreaks | ||
Adds soft line breaks to encoded strings. Needed for folding body lines. | ||
#### #encodeFlowed | ||
libmime.addSoftLinebreaks(str, encoding, lineLengthMax) -> String | ||
Adds soft line breaks to content marked with `format=flowed` options to ensure that no line in the message is never longer than lineLength. | ||
* **str** Encoded string that requires wrapping | ||
* **encoding** Either Q for quoted-printable, B for base64 (the default) or F for `format=flowed` | ||
* **lineLengthMax** Maximum line length without line breaks(defaults to 76) | ||
libmime.encodeFlowed(str [, lineLength]) → String | ||
#### #flowedDecode | ||
* **str** Plaintext string that requires wrapping | ||
* **lineLength** (defaults to 76) Maximum length of a line | ||
#### #decodeFlowed | ||
Unwraps a plaintext string in format=flowed wrapping. | ||
libmime.flowedDecode(str [, delSp]) -> String | ||
libmime.decodeFlowed(str [, delSp]) → String | ||
@@ -163,32 +121,21 @@ * **str** Plaintext string with format=flowed to decode | ||
#### #headerLineEncode | ||
#### #decodeHeader | ||
Encodes and folds a header line for a MIME message header. Shorthand for `mimeWordsEncode` + `foldLines`. | ||
libmime.headerLineEncode(key, value[, fromCharset]) | ||
* **key** - Key name, will not be encoded | ||
* **value** - Value to be encoded | ||
* **fromCharset** - If the `value` parameter is a Buffer object, use this charset to decode the value to unicode before encoding | ||
#### #headerLineDecode | ||
Unfolds a header line and splits it to key and value pair. The return value is in the form of `{key: 'subject', value: 'test'}`. The value is not mime word decoded, you need to do your own decoding based on the rules for the specific header key. | ||
libmime.headerLineDecode(headerLine) -> Object | ||
libmime.decodeHeader(headerLine) → Object | ||
* **headerLine** - Single header line, might include linebreaks as well if folded | ||
#### #headerLinesDecode | ||
#### #decodeHeaders | ||
Parses a block of header lines. Does not decode mime words as every header | ||
might have its own rules (eg. formatted email addresses and such). | ||
Parses a block of header lines. Does not decode mime words as every header might have its own rules (eg. formatted email addresses and such). | ||
Return value is an object of headers, where header keys are object keys. NB! Several values with the same key make up an array of values for the same key. | ||
Return value is an object of headers, where header keys are object keys and values are arrays. | ||
libmime.headerLinesDecode(headers) -> Object | ||
libmime.decodeHeaders(headers) → Object | ||
* **headers** - Headers string | ||
#### #parseStructuredHeaderValue | ||
#### #parseHeaderValue | ||
@@ -198,3 +145,3 @@ Parses a header value with `key=value` arguments into a structured object. Useful when dealing with | ||
parseStructuredHeaderValue(valueString) -> Object | ||
parseHeaderValue(valueString) → Object | ||
@@ -206,3 +153,3 @@ * **valueString** - a header value without the key | ||
```javascript | ||
parseStructuredHeaderValue('content-type: text/plain; CHARSET="UTF-8"'); | ||
parseHeaderValue('content-type: text/plain; CHARSET="UTF-8"'); | ||
``` | ||
@@ -221,17 +168,17 @@ | ||
#### #buildStructuredHeaderValue | ||
#### #buildHeaderValue | ||
Joins structured header value together as 'value; param1=value1; param2=value2' | ||
buildStructuredHeaderValue(structuredHeader) -> String | ||
buildHeaderValue(structuredHeader) → String | ||
* **structuredHeader** - a header value formatted with `parseStructuredHeaderValue` | ||
* **structuredHeader** - a header value formatted with `parseHeaderValue` | ||
`filename` argument is encoded with continuation encoding if needed | ||
#### #continuationEncode | ||
#### #buildHeaderParam | ||
Encodes and splits a header param value according to [RFC2231](https://tools.ietf.org/html/rfc2231#section-3) Parameter Value Continuations. | ||
libmime.continuationEncode(key, str, maxLength [, fromCharset]) -> Array | ||
libmime.buildHeaderParam(key, str, maxLength) → Array | ||
@@ -241,3 +188,2 @@ * **key** - Parameter key (eg. `filename`) | ||
* **maxLength** - Maximum length of the encoded string part (not line length). Defaults to 50 | ||
* **fromCharset** - If `str` is a Buffer object, use this charset to decode the value to unicode before encoding | ||
@@ -249,4 +195,4 @@ The method returns an array of encoded parts with the following structure: `[{key:'...', value: '...'}]` | ||
``` | ||
libmime.continuationEncode('filename', 'filename õäöü.txt', 20); | ||
-> | ||
libmime.buildHeaderParam('filename', 'filename õäöü.txt', 20); | ||
→ | ||
[ { key: 'filename*0*', value: 'utf-8\'\'filename%20' }, | ||
@@ -270,3 +216,3 @@ { key: 'filename*1*', value: '%C3%B5%C3%A4%C3%B6' }, | ||
libmime.detectExtension(mimeType) -> String | ||
libmime.detectExtension(mimeType) → String | ||
@@ -283,3 +229,3 @@ * **mimeType** - Content type to be checked for | ||
libmime.detectMimeType(extension) -> String | ||
libmime.detectMimeType(extension) → String | ||
@@ -286,0 +232,0 @@ * **extension** Extension (or filename) to be checked for |
'use strict'; | ||
var libcharset = require('./charset'); | ||
var libbase64 = require('./base64'); | ||
var libbase64 = require('libbase64'); | ||
var libqp = require('libqp'); | ||
var mimetypes = require('./mimetypes'); | ||
var libmime = module.exports = { | ||
/** | ||
* Encodes all non printable and non ascii bytes to =XX form, where XX is the | ||
* byte value in hex. This function does not convert linebreaks etc. it | ||
* only escapes character sequences | ||
* | ||
* @param {String|Buffer} data Either a string or an Buffer | ||
* @param {String} [fromCharset='UTF-8'] Source encoding | ||
* @return {String} Mime encoded string | ||
*/ | ||
mimeEncode: function(data, fromCharset) { | ||
fromCharset = fromCharset || 'UTF-8'; | ||
var buffer = libcharset.convert(data || '', fromCharset), | ||
ranges = [ | ||
[0x09], | ||
[0x0A], | ||
[0x0D], | ||
[0x20], | ||
[0x21], | ||
[0x23, 0x3C], | ||
[0x3E], | ||
[0x40, 0x5E], | ||
[0x60, 0x7E] | ||
], | ||
result = ''; | ||
for (var i = 0, len = buffer.length; i < len; i++) { | ||
if (libmime._checkRanges(buffer[i], ranges)) { | ||
result += String.fromCharCode(buffer[i]); | ||
continue; | ||
} | ||
result += '=' + (buffer[i] < 0x10 ? '0' : '') + buffer[i].toString(16).toUpperCase(); | ||
} | ||
return result; | ||
}, | ||
/** | ||
* Decodes mime encoded string to an unicode string | ||
* Checks if a value is plaintext string (uses only printable 7bit chars) | ||
* | ||
* @param {String} str Mime encoded string | ||
* @param {String} [fromCharset='UTF-8'] Source encoding | ||
* @return {String} Decoded unicode string | ||
* @param {String} value String to be tested | ||
* @returns {Boolean} true if it is a plaintext string | ||
*/ | ||
mimeDecode: function(str, fromCharset) { | ||
str = (str || '').toString(); | ||
fromCharset = fromCharset || 'UTF-8'; | ||
var encodedBytesCount = (str.match(/\=[\da-fA-F]{2}/g) || []).length, | ||
bufferLength = str.length - encodedBytesCount * 2, | ||
chr, hex, | ||
buffer = new Buffer(bufferLength), | ||
bufferPos = 0; | ||
for (var i = 0, len = str.length; i < len; i++) { | ||
chr = str.charAt(i); | ||
if (chr === '=' && (hex = str.substr(i + 1, 2)) && /[\da-fA-F]{2}/.test(hex)) { | ||
buffer[bufferPos++] = parseInt(hex, 16); | ||
i += 2; | ||
continue; | ||
} | ||
buffer[bufferPos++] = chr.charCodeAt(0); | ||
} | ||
return libcharset.decode(buffer, fromCharset); | ||
}, | ||
/** | ||
* Encodes a string or an typed array of given charset into unicode | ||
* base64 string. Also adds line breaks | ||
* | ||
* @param {String|Buffer} data String to be base64 encoded | ||
* @param {String} [fromCharset='UTF-8'] | ||
* @return {String} Base64 encoded string | ||
*/ | ||
base64Encode: function(data, fromCharset) { | ||
var buf; | ||
if (fromCharset !== 'binary' && typeof data !== 'string') { | ||
buf = libcharset.convert(data || '', fromCharset); | ||
isPlainText: function(value) { | ||
if (typeof value !== 'string' || /[\x00-\x08\x0b\x0c\x0e-\x1f\u0080-\uFFFF]/.test(value)) { | ||
return false; | ||
} else { | ||
buf = data; | ||
return true; | ||
} | ||
return libbase64.encode(buf); | ||
}, | ||
/** | ||
* Decodes a base64 string of any charset into an unicode string | ||
* Checks if a multi line string containes lines longer than the selected value. | ||
* | ||
* @param {String} str Base64 encoded string | ||
* @param {String} [fromCharset='UTF-8'] Original charset of the base64 encoded string | ||
* @return {String} Decoded unicode string | ||
* Useful when detecting if a mail message needs any processing at all – | ||
* if only plaintext characters are used and lines are short, then there is | ||
* no need to encode the values in any way. If the value is plaintext but has | ||
* longer lines then allowed, then use format=flowed | ||
* | ||
* @param {Number} lineLength Max line length to check for | ||
* @returns {Boolean} Returns true if there is at least one line longer than lineLength chars | ||
*/ | ||
base64Decode: function(str, fromCharset) { | ||
var buf = libbase64.decode(str || '', 'buffer'); | ||
return libcharset.decode(buf, fromCharset); | ||
hasLongerLines: function(str, lineLength) { | ||
return new RegExp('^.{' + (lineLength + 1) + ',}', 'm').test(str); | ||
}, | ||
@@ -114,3 +46,3 @@ | ||
*/ | ||
flowedDecode: function(str, delSp) { | ||
decodeFlowed: function(str, delSp) { | ||
str = (str || '').toString(); | ||
@@ -129,3 +61,3 @@ | ||
} | ||
if (/ $/.test(previousValue) && !/(^|\n)\-\- $/.test(previousValue) || index === 1) { | ||
if (/ $/.test(previousValue) && !/(^|\n)\-\- $/.test(previousValue) || index === 1) { | ||
return body + currentValue; | ||
@@ -142,42 +74,23 @@ } else { | ||
/** | ||
* Encodes a string or an Buffer into a quoted printable encoding | ||
* This is almost the same as mimeEncode, except line breaks will be changed | ||
* as well to ensure that the lines are never longer than allowed length | ||
* Adds soft line breaks to content marked with format=flowed to | ||
* ensure that no line in the message is never longer than lineLength | ||
* | ||
* @param {String|Buffer} data String or an Buffer to mime encode | ||
* @param {String} [fromCharset='UTF-8'] Original charset of the string | ||
* @return {String} Mime encoded string | ||
* @param {String} str Plaintext string that requires wrapping | ||
* @param {Number} [lineLength=76] Maximum length of a line | ||
* @return {String} String with forced line breaks | ||
*/ | ||
quotedPrintableEncode: function(data, fromCharset) { | ||
var mimeEncodedStr = libmime.mimeEncode(data, fromCharset); | ||
encodeFlowed: function(str, lineLength) { | ||
lineLength = lineLength || 76; | ||
mimeEncodedStr = mimeEncodedStr. | ||
// fix line breaks, ensure <CR><LF> | ||
replace(/\r?\n|\r/g, '\r\n'). | ||
// replace spaces in the end of lines | ||
replace(/[\t ]+$/gm, function(spaces) { | ||
return spaces.replace(/ /g, '=20').replace(/\t/g, '=09'); | ||
var flowed = []; | ||
str.split(/\r?\n/).forEach(function(line) { | ||
flowed.push(libmime.foldLines(line. | ||
// space stuffing http://tools.ietf.org/html/rfc3676#section-4.2 | ||
replace(/^( |From|>)/igm, ' $1'), | ||
lineLength, true)); | ||
}); | ||
// add soft line breaks to ensure line lengths sjorter than 76 bytes | ||
return mimeEncodedStr; | ||
return flowed.join('\r\n'); | ||
}, | ||
/** | ||
* Decodes a string from a quoted printable encoding. This is almost the | ||
* same as mimeDecode, except line breaks will be changed as well | ||
* | ||
* @param {String} str Mime encoded string to decode | ||
* @param {String} [fromCharset='UTF-8'] Original charset of the string | ||
* @return {String} Mime decoded string | ||
*/ | ||
quotedPrintableDecode: function(str, fromCharset) { | ||
str = (str || '').toString(); | ||
// remove soft line breaks | ||
str = str.replace(/\=(?:\r?\n|$)/g, ''); | ||
return libmime.mimeDecode(str, fromCharset); | ||
}, | ||
/** | ||
* Encodes a string or an Buffer to an UTF-8 MIME Word (rfc2047) | ||
@@ -188,13 +101,6 @@ * | ||
* @param {Number} [maxLength=0] If set, split mime words into several chunks if needed | ||
* @param {String} [fromCharset='UTF-8'] Source sharacter set | ||
* @return {String} Single or several mime words joined together | ||
*/ | ||
mimeWordEncode: function(data, mimeWordEncoding, maxLength, fromCharset) { | ||
encodeWord: function(data, mimeWordEncoding, maxLength) { | ||
mimeWordEncoding = (mimeWordEncoding || 'Q').toString().toUpperCase().trim().charAt(0); | ||
if (!fromCharset && typeof maxLength === 'string' && !maxLength.match(/^[0-9]+$/)) { | ||
fromCharset = maxLength; | ||
maxLength = undefined; | ||
} | ||
maxLength = maxLength || 0; | ||
@@ -211,9 +117,8 @@ | ||
if (mimeWordEncoding === 'Q') { | ||
encodedStr = libmime.mimeEncode(data, fromCharset); | ||
encodedStr = encodedStr.replace(/[\r\n\t_]/g, function(chr) { | ||
var code = chr.charCodeAt(0); | ||
return '=' + (code < 0x10 ? '0' : '') + code.toString(16).toUpperCase(); | ||
}).replace(/\s/g, '_'); | ||
encodedStr = libqp.encode(data).replace(/[_?\r\n\t"]/g, function(chr) { | ||
var ord = chr.charCodeAt(0).toString(16).toUpperCase(); | ||
return '=' + (ord.length === 1 ? '0' + ord : ord); | ||
}).replace(/%20| /g, '_'); | ||
} else if (mimeWordEncoding === 'B') { | ||
encodedStr = typeof data === 'string' ? data : libmime.decode(data, fromCharset); | ||
encodedStr = typeof data === 'string' ? data : libbase64.encode(data); | ||
maxLength = Math.max(3, (maxLength - maxLength % 4) / 4 * 3); | ||
@@ -224,3 +129,3 @@ } | ||
if (mimeWordEncoding === 'Q') { | ||
encodedStr = libmime._splitMimeEncodedString(encodedStr, maxLength).join('?= =?' + toCharset + '?' + mimeWordEncoding + '?'); | ||
encodedStr = splitMimeEncodedString(encodedStr, maxLength).join('?= =?' + toCharset + '?' + mimeWordEncoding + '?'); | ||
} else { | ||
@@ -235,3 +140,3 @@ | ||
if (parts.length > 1) { | ||
return '=?' + toCharset + '?' + mimeWordEncoding + '?' + parts.join('?= =?' + toCharset + '?' + mimeWordEncoding + '?') + '?='; | ||
encodedStr = parts.join('?= =?' + toCharset + '?' + mimeWordEncoding + '?'); | ||
} else { | ||
@@ -242,3 +147,3 @@ encodedStr = parts.join(''); | ||
} else if (mimeWordEncoding === 'B') { | ||
encodedStr = libbase64.encode(encodedStr); | ||
encodedStr = libbase64.encode(data); | ||
} | ||
@@ -250,29 +155,2 @@ | ||
/** | ||
* Finds word sequences with non ascii text and converts these to mime words | ||
* | ||
* @param {String|Buffer} data String to be encoded | ||
* @param {String} mimeWordEncoding='Q' Encoding for the mime word, either Q or B | ||
* @param {Number} [maxLength=0] If set, split mime words into several chunks if needed | ||
* @param {String} [fromCharset='UTF-8'] Source sharacter set | ||
* @return {String} String with possible mime words | ||
*/ | ||
mimeWordsEncode: function(data, mimeWordEncoding, maxLength, fromCharset) { | ||
if (!fromCharset && typeof maxLength === 'string' && !maxLength.match(/^[0-9]+$/)) { | ||
fromCharset = maxLength; | ||
maxLength = undefined; | ||
} | ||
maxLength = maxLength || 0; | ||
var decodedValue = libcharset.decode(libcharset.convert((data || ''), fromCharset)), | ||
encodedValue; | ||
encodedValue = decodedValue.replace(/([^\s\u0080-\uFFFF]*[\u0080-\uFFFF]+[^\s\u0080-\uFFFF]*(?:\s+[^\s\u0080-\uFFFF]*[\u0080-\uFFFF]+[^\s\u0080-\uFFFF]*\s*)?)+/g, function(match) { | ||
return match.length ? libmime.mimeWordEncode(match, mimeWordEncoding || 'Q', maxLength) : ''; | ||
}); | ||
return encodedValue; | ||
}, | ||
/** | ||
* Decode a complete mime word encoded string | ||
@@ -283,3 +161,3 @@ * | ||
*/ | ||
mimeWordDecode: function(str) { | ||
decodeWord: function(str) { | ||
str = (str || '').toString().trim(); | ||
@@ -300,12 +178,38 @@ | ||
encoding = (match[2] || 'Q').toString().toUpperCase(); | ||
str = (match[3] || '').replace(/_/g, ' '); | ||
str = (match[3] || '').replace(/_/g, ' ').replace(/ $/, '=20'); | ||
if (encoding === 'B') { | ||
return libmime.base64Decode(str, fromCharset); | ||
return libcharset.decode(libbase64.decode(str), fromCharset); | ||
} else if (encoding === 'Q') { | ||
return libmime.mimeDecode(str, fromCharset); | ||
return libcharset.decode(libqp.decode(str), fromCharset); | ||
} else { | ||
return str; | ||
} | ||
}, | ||
/** | ||
* Finds word sequences with non ascii text and converts these to mime words | ||
* | ||
* @param {String|Buffer} data String to be encoded | ||
* @param {String} mimeWordEncoding='Q' Encoding for the mime word, either Q or B | ||
* @param {Number} [maxLength=0] If set, split mime words into several chunks if needed | ||
* @param {String} [fromCharset='UTF-8'] Source sharacter set | ||
* @return {String} String with possible mime words | ||
*/ | ||
encodeWords: function(data, mimeWordEncoding, maxLength, fromCharset) { | ||
if (!fromCharset && typeof maxLength === 'string' && !maxLength.match(/^[0-9]+$/)) { | ||
fromCharset = maxLength; | ||
maxLength = undefined; | ||
} | ||
maxLength = maxLength || 0; | ||
var decodedValue = libcharset.decode(libcharset.convert((data || ''), fromCharset)), | ||
encodedValue; | ||
encodedValue = decodedValue.replace(/([^\s\u0080-\uFFFF]*[\u0080-\uFFFF]+[^\s\u0080-\uFFFF]*(?:\s+[^\s\u0080-\uFFFF]*[\u0080-\uFFFF]+[^\s\u0080-\uFFFF]*\s*)?)+/g, function(match) { | ||
return match.length ? libmime.encodeWord(match, mimeWordEncoding || 'Q', maxLength) : ''; | ||
}); | ||
return encodedValue; | ||
}, | ||
@@ -319,3 +223,3 @@ | ||
*/ | ||
mimeWordsDecode: function(str) { | ||
decodeWords: function(str) { | ||
str = (str || '').toString(); | ||
@@ -325,3 +229,3 @@ str = str. | ||
replace(/\=\?([\w_\-\*]+)\?([QqBb])\?[^\?]+\?\=/g, function(mimeWord) { | ||
return libmime.mimeWordDecode(mimeWord); | ||
return libmime.decodeWord(mimeWord); | ||
}); | ||
@@ -333,61 +237,2 @@ | ||
/** | ||
* Folds long lines, useful for folding header lines (afterSpace=false) and | ||
* flowed text (afterSpace=true) | ||
* | ||
* @param {String} str String to be folded | ||
* @param {Number} [lineLengthMax=76] Maximum length of a line | ||
* @param {Boolean} afterSpace If true, leave a space in th end of a line | ||
* @return {String} String with folded lines | ||
*/ | ||
foldLines: function(str, lineLengthMax, afterSpace) { | ||
str = (str || '').toString(); | ||
lineLengthMax = lineLengthMax || 76; | ||
var pos = 0, | ||
len = str.length, | ||
result = '', | ||
line, match; | ||
while (pos < len) { | ||
line = str.substr(pos, lineLengthMax); | ||
if (line.length < lineLengthMax) { | ||
result += line; | ||
break; | ||
} | ||
if ((match = line.match(/^[^\n\r]*(\r?\n|\r)/))) { | ||
line = match[0]; | ||
result += line; | ||
pos += line.length; | ||
continue; | ||
} else if ((match = line.match(/(\s+)[^\s]*$/)) && match[0].length - (afterSpace ? (match[1] || '').length : 0) < line.length) { | ||
line = line.substr(0, line.length - (match[0].length - (afterSpace ? (match[1] || '').length : 0))); | ||
} else if ((match = str.substr(pos + line.length).match(/^[^\s]+(\s*)/))) { | ||
line = line + match[0].substr(0, match[0].length - (!afterSpace ? (match[1] || '').length : 0)); | ||
} | ||
result += line; | ||
pos += line.length; | ||
if (pos < len) { | ||
result += '\r\n'; | ||
} | ||
} | ||
return result; | ||
}, | ||
/** | ||
* Encodes and folds a header line for a MIME message header. | ||
* Shorthand for mimeWordsEncode + foldLines | ||
* | ||
* @param {String} key Key name, will not be encoded | ||
* @param {String|Buffer} value Value to be encoded | ||
* @param {String} [fromCharset='UTF-8'] Character set of the value | ||
* @return {String} encoded and folded header line | ||
*/ | ||
headerLineEncode: function(key, value, fromCharset) { | ||
var encodedValue = libmime.mimeWordsEncode(value, 'Q', 52, fromCharset); | ||
return libmime.foldLines(key + ': ' + encodedValue, 76); | ||
}, | ||
/** | ||
* Splits a string by : | ||
@@ -400,6 +245,6 @@ * The result is not mime word decoded, you need to do your own decoding based | ||
*/ | ||
headerLineDecode: function(headerLine) { | ||
decodeHeader: function(headerLine) { | ||
var line = (headerLine || '').toString().replace(/(?:\r?\n|\r)[ \t]*/g, ' ').trim(), | ||
match = line.match(/^\s*([^:]+):(.*)$/), | ||
key = (match && match[1] || '').trim(), | ||
key = (match && match[1] || '').trim().toLowerCase(), | ||
value = (match && match[2] || '').trim(); | ||
@@ -420,6 +265,5 @@ | ||
*/ | ||
headerLinesDecode: function(headers) { | ||
decodeHeaders: function(headers) { | ||
var lines = headers.split(/\r?\n|\r/), | ||
headersObj = {}, | ||
key, value, | ||
header, | ||
@@ -436,10 +280,7 @@ i, len; | ||
for (i = 0, len = lines.length; i < len; i++) { | ||
header = libmime.headerLineDecode(lines[i]); | ||
key = (header.key || '').toString().toLowerCase().trim(); | ||
value = header.value || ''; | ||
if (!headersObj[key]) { | ||
headersObj[key] = value; | ||
header = libmime.decodeHeader(lines[i]); | ||
if (!headersObj[header.key]) { | ||
headersObj[header.key] = [header.value]; | ||
} else { | ||
headersObj[key] = [].concat(headersObj[key], value); | ||
headersObj[header.key].push(header.value); | ||
} | ||
@@ -457,3 +298,3 @@ } | ||
*/ | ||
buildStructuredHeaderValue: function(structured) { | ||
buildHeaderValue: function(structured) { | ||
var paramsArray = []; | ||
@@ -463,4 +304,5 @@ | ||
// filename might include unicode characters so it is a special case | ||
if (param === 'filename') { | ||
libmime.continuationEncode(param, structured.params[param], 50).forEach(function(encodedParam) { | ||
var value = structured.params[param]; | ||
if (param === 'filename' || !libmime.isPlainText(value) || value.length >= 75) { | ||
libmime.buildHeaderParam(param, value, 50).forEach(function(encodedParam) { | ||
if (encodedParam.key === param) { | ||
@@ -473,6 +315,6 @@ paramsArray.push(encodedParam.key + '=' + encodedParam.value); | ||
} else { | ||
if (structured.params[param].match(/[\s'"\\;\/=]|^\-/g)) { | ||
paramsArray.push(param + '="' + structured.params[param].replace(/(["\\])/g, "\\$1") + '"'); | ||
if (value.match(/[\s'"\\;\/=]|^\-/g)) { | ||
paramsArray.push(param + '="' + value.replace(/(["\\])/g, "\\$1") + '"'); | ||
} else { | ||
paramsArray.push(param + '=' + structured.params[param]); | ||
paramsArray.push(param + '=' + value); | ||
} | ||
@@ -489,3 +331,3 @@ } | ||
* | ||
* parseStructuredHeaderValue('content-type: text/plain; CHARSET='UTF-8'') -> | ||
* parseHeaderValue('content-type: text/plain; CHARSET='UTF-8'') -> | ||
* { | ||
@@ -501,3 +343,3 @@ * 'value': 'text/plain', | ||
*/ | ||
parseStructuredHeaderValue: function(str) { | ||
parseHeaderValue: function(str) { | ||
var response = { | ||
@@ -641,3 +483,3 @@ value: false, | ||
*/ | ||
continuationEncode: function(key, data, maxLength, fromCharset) { | ||
buildHeaderParam: function(key, data, maxLength, fromCharset) { | ||
var list = []; | ||
@@ -754,28 +596,2 @@ var encodedStr = typeof data === 'string' ? data : libmime.decode(data, fromCharset); | ||
/** | ||
* Adds soft line breaks (the ones that will be stripped out when decoding) to | ||
* ensure that no line in the message is never longer than 76 symbols | ||
* | ||
* Lines can't be longer than 76 + <CR><LF> = 78 bytes | ||
* http://tools.ietf.org/html/rfc2045#section-6.7 | ||
* | ||
* @param {String} str Encoded string | ||
* @param {String} encoding Either "Q" or "B" (the default) | ||
* @param {Number} [lineLengthMax=76] maximum line length | ||
* @return {String} String with forced line breaks | ||
*/ | ||
addSoftLinebreaks: function(str, encoding, lineLengthMax) { | ||
lineLengthMax = lineLengthMax || 76; | ||
encoding = (encoding || 'b').toString().toLowerCase().trim(); | ||
if (encoding === 'q') { | ||
return libmime._addQPSoftLinebreaks(str, lineLengthMax); | ||
} else if (encoding === 'f') { | ||
return libmime._addFlowedSoftLinebreaks(str, lineLengthMax); | ||
} else { | ||
return libmime._addBase64SoftLinebreaks(str, lineLengthMax); | ||
} | ||
}, | ||
/** | ||
* Returns file extension for a content type string. If no suitable extensions | ||
@@ -843,184 +659,90 @@ * are found, 'bin' is used as the default extension | ||
/** | ||
* Splits a mime encoded string. Needed for dividing mime words into smaller chunks | ||
* Folds long lines, useful for folding header lines (afterSpace=false) and | ||
* flowed text (afterSpace=true) | ||
* | ||
* @param {String} str Mime encoded string to be split up | ||
* @param {Number} maxlen Maximum length of characters for one part (minimum 12) | ||
* @return {Array} Split string | ||
* @param {String} str String to be folded | ||
* @param {Number} [lineLength=76] Maximum length of a line | ||
* @param {Boolean} afterSpace If true, leave a space in th end of a line | ||
* @return {String} String with folded lines | ||
*/ | ||
_splitMimeEncodedString: function(str, maxlen) { | ||
var curLine, match, chr, done, | ||
lines = []; | ||
foldLines: function(str, lineLength, afterSpace) { | ||
str = (str || '').toString(); | ||
lineLength = lineLength || 76; | ||
// require at least 12 symbols to fit possible 4 octet UTF-8 sequences | ||
maxlen = Math.max(maxlen || 0, 12); | ||
while (str.length) { | ||
curLine = str.substr(0, maxlen); | ||
// move incomplete escaped char back to main | ||
if ((match = curLine.match(/\=[0-9A-F]?$/i))) { | ||
curLine = curLine.substr(0, match.index); | ||
} | ||
done = false; | ||
while (!done) { | ||
done = true; | ||
// check if not middle of a unicode char sequence | ||
if ((match = str.substr(curLine.length).match(/^\=([0-9A-F]{2})/i))) { | ||
chr = parseInt(match[1], 16); | ||
// invalid sequence, move one char back anc recheck | ||
if (chr < 0xC2 && chr > 0x7F) { | ||
curLine = curLine.substr(0, curLine.length - 3); | ||
done = false; | ||
} | ||
} | ||
} | ||
if (curLine.length) { | ||
lines.push(curLine); | ||
} | ||
str = str.substr(curLine.length); | ||
} | ||
return lines; | ||
}, | ||
/** | ||
* Adds soft line breaks (the ones that will be stripped out when decoding base64) to | ||
* ensure that no line in the message is never longer than lineLengthMax | ||
* | ||
* @param {String} base64EncodedStr String in BASE64 encoding | ||
* @param {Number} lineLengthMax Maximum length of a line | ||
* @return {String} String with forced line breaks | ||
*/ | ||
_addBase64SoftLinebreaks: function(base64EncodedStr, lineLengthMax) { | ||
base64EncodedStr = (base64EncodedStr || '').toString().trim(); | ||
return base64EncodedStr.replace(new RegExp('.{' + lineLengthMax + '}', 'g'), '$&\r\n').trim(); | ||
}, | ||
/** | ||
* Adds soft line breaks to content marked with format=flowed to | ||
* ensure that no line in the message is never longer than lineLengthMax | ||
* | ||
* @param {String} str Plaintext string that requires wrapping | ||
* @param {Number} lineLengthMax Maximum length of a line | ||
* @return {String} String with forced line breaks | ||
*/ | ||
_addFlowedSoftLinebreaks: function(str, lineLengthMax) { | ||
var flowed = []; | ||
str.split(/\r?\n/).forEach(function(line) { | ||
flowed.push(libmime.foldLines(line. | ||
// space stuffing http://tools.ietf.org/html/rfc3676#section-4.2 | ||
replace(/^( |From|>)/igm, ' $1'), | ||
lineLengthMax, true)); | ||
}); | ||
return flowed.join('\r\n'); | ||
}, | ||
/** | ||
* Adds soft line breaks(the ones that will be stripped out when decoding QP) to * ensure that no line in the message is never longer than lineLengthMax * * Not sure of how and why this works, but at least it seems to be working: / | ||
* | ||
* @param {String} qpEncodedStr String in Quoted-Printable encoding | ||
* @param {Number} lineLengthMax Maximum length of a line | ||
* @return {String} String with forced line breaks | ||
*/ | ||
_addQPSoftLinebreaks: function(qpEncodedStr, lineLengthMax) { | ||
qpEncodedStr = (qpEncodedStr || '').toString(); | ||
var pos = 0, | ||
len = qpEncodedStr.length, | ||
match, code, line, | ||
lineMargin = Math.floor(lineLengthMax / 3), | ||
result = ''; | ||
len = str.length, | ||
result = '', | ||
line, match; | ||
// insert soft linebreaks where needed | ||
while (pos < len) { | ||
line = qpEncodedStr.substr(pos, lineLengthMax); | ||
if ((match = line.match(/\r\n/))) { | ||
line = line.substr(0, match.index + match[0].length); | ||
line = str.substr(pos, lineLength); | ||
if (line.length < lineLength) { | ||
result += line; | ||
pos += line.length; | ||
continue; | ||
break; | ||
} | ||
if (line.substr(-1) === '\n') { | ||
// nothing to change here | ||
if ((match = line.match(/^[^\n\r]*(\r?\n|\r)/))) { | ||
line = match[0]; | ||
result += line; | ||
pos += line.length; | ||
continue; | ||
} else if ((match = line.substr(-lineMargin).match(/\n.*?$/))) { | ||
// truncate to nearest line break | ||
line = line.substr(0, line.length - (match[0].length - 1)); | ||
result += line; | ||
pos += line.length; | ||
continue; | ||
} else if (line.length > lineLengthMax - lineMargin && (match = line.substr(-lineMargin).match(/[ \t\.,!\?][^ \t\.,!\?]*$/))) { | ||
// truncate to nearest space | ||
line = line.substr(0, line.length - (match[0].length - 1)); | ||
} else if (line.substr(-1) === '\r') { | ||
line = line.substr(0, line.length - 1); | ||
} else { | ||
if (line.match(/\=[\da-f]{0,2}$/i)) { | ||
} else if ((match = line.match(/(\s+)[^\s]*$/)) && match[0].length - (afterSpace ? (match[1] || '').length : 0) < line.length) { | ||
line = line.substr(0, line.length - (match[0].length - (afterSpace ? (match[1] || '').length : 0))); | ||
} else if ((match = str.substr(pos + line.length).match(/^[^\s]+(\s*)/))) { | ||
line = line + match[0].substr(0, match[0].length - (!afterSpace ? (match[1] || '').length : 0)); | ||
} | ||
// push incomplete encoding sequences to the next line | ||
if ((match = line.match(/\=[\da-f]{0,1}$/i))) { | ||
line = line.substr(0, line.length - match[0].length); | ||
} | ||
result += line; | ||
pos += line.length; | ||
if (pos < len) { | ||
result += '\r\n'; | ||
} | ||
} | ||
// ensure that utf-8 sequences are not split | ||
while (line.length > 3 && line.length < len - pos && !line.match(/^(?:=[\da-f]{2}){1,4}$/i) && (match = line.match(/\=[\da-f]{2}$/ig))) { | ||
code = parseInt(match[0].substr(1, 2), 16); | ||
if (code < 128) { | ||
break; | ||
} | ||
return result; | ||
} | ||
}; | ||
line = line.substr(0, line.length - 3); | ||
/** | ||
* Splits a mime encoded string. Needed for dividing mime words into smaller chunks | ||
* | ||
* @param {String} str Mime encoded string to be split up | ||
* @param {Number} maxlen Maximum length of characters for one part (minimum 12) | ||
* @return {Array} Split string | ||
*/ | ||
function splitMimeEncodedString(str, maxlen) { | ||
var curLine, match, chr, done, | ||
lines = []; | ||
if (code >= 0xC0) { | ||
break; | ||
} | ||
} | ||
// require at least 12 symbols to fit possible 4 octet UTF-8 sequences | ||
maxlen = Math.max(maxlen || 0, 12); | ||
} | ||
} | ||
while (str.length) { | ||
curLine = str.substr(0, maxlen); | ||
if (pos + line.length < len && line.substr(-1) !== '\n') { | ||
if (line.length === 76 && line.match(/\=[\da-f]{2}$/i)) { | ||
line = line.substr(0, line.length - 3); | ||
} else if (line.length === 76) { | ||
line = line.substr(0, line.length - 1); | ||
// move incomplete escaped char back to main | ||
if ((match = curLine.match(/\=[0-9A-F]?$/i))) { | ||
curLine = curLine.substr(0, match.index); | ||
} | ||
done = false; | ||
while (!done) { | ||
done = true; | ||
// check if not middle of a unicode char sequence | ||
if ((match = str.substr(curLine.length).match(/^\=([0-9A-F]{2})/i))) { | ||
chr = parseInt(match[1], 16); | ||
// invalid sequence, move one char back anc recheck | ||
if (chr < 0xC2 && chr > 0x7F) { | ||
curLine = curLine.substr(0, curLine.length - 3); | ||
done = false; | ||
} | ||
pos += line.length; | ||
line += '=\r\n'; | ||
} else { | ||
pos += line.length; | ||
} | ||
result += line; | ||
} | ||
return result; | ||
}, | ||
/** | ||
* Checks if a number is in specified ranges or not | ||
* | ||
* @param {Number} nr Number to check for | ||
* @ranges {Array} ranges Array of range duples | ||
* @return {Boolean} Returns true, if nr was found to be at least one of the specified ranges | ||
*/ | ||
_checkRanges: function(nr, ranges) { | ||
for (var i = ranges.length - 1; i >= 0; i--) { | ||
if (!ranges[i].length) { | ||
continue; | ||
} | ||
if (ranges[i].length === 1 && nr === ranges[i][0]) { | ||
return true; | ||
} | ||
if (ranges[i].length === 2 && nr >= ranges[i][0] && nr <= ranges[i][1]) { | ||
return true; | ||
} | ||
if (curLine.length) { | ||
lines.push(curLine); | ||
} | ||
return false; | ||
str = str.substr(curLine.length); | ||
} | ||
}; | ||
return lines; | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
129110
3
7
2716
224
+ Addedlibbase64@^0.1.0
+ Addedlibqp@^0.1.1
+ Addedlibbase64@0.1.0(transitive)
+ Addedlibqp@0.1.1(transitive)