| 'use strict'; |
| |
| const Transform = require('stream').Transform; |
| |
| /** |
| * Encodes a Buffer into a base64 encoded string |
| * |
| * @param {Buffer} buffer Buffer to convert |
| * @returns {String} base64 encoded string |
| */ |
| function encode(buffer) { |
| if (typeof buffer === 'string') { |
| buffer = new Buffer(buffer, 'utf-8'); |
| } |
| |
| return buffer.toString('base64'); |
| } |
| |
| /** |
| * Adds soft line breaks to a base64 string |
| * |
| * @param {String} str base64 encoded string that might need line wrapping |
| * @param {Number} [lineLength=76] Maximum allowed length for a line |
| * @returns {String} Soft-wrapped base64 encoded string |
| */ |
| function wrap(str, lineLength) { |
| str = (str || '').toString(); |
| lineLength = lineLength || 76; |
| |
| if (str.length <= lineLength) { |
| return str; |
| } |
| |
| let result = []; |
| let pos = 0; |
| let chunkLength = lineLength * 1024; |
| while (pos < str.length) { |
| let wrappedLines = str.substr(pos, chunkLength).replace(new RegExp('.{' + lineLength + '}', 'g'), '$&\r\n').trim(); |
| result.push(wrappedLines); |
| pos += chunkLength; |
| } |
| |
| return result.join('\r\n').trim(); |
| } |
| |
| /** |
| * Creates a transform stream for encoding data to base64 encoding |
| * |
| * @constructor |
| * @param {Object} options Stream options |
| * @param {Number} [options.lineLength=76] Maximum lenght for lines, set to false to disable wrapping |
| */ |
| class Encoder extends Transform { |
| constructor(options) { |
| super(); |
| // init Transform |
| this.options = options || {}; |
| |
| if (this.options.lineLength !== false) { |
| this.options.lineLength = this.options.lineLength || 76; |
| } |
| |
| this._curLine = ''; |
| this._remainingBytes = false; |
| |
| this.inputBytes = 0; |
| this.outputBytes = 0; |
| } |
| |
| _transform(chunk, encoding, done) { |
| let b64; |
| |
| if (encoding !== 'buffer') { |
| chunk = new Buffer(chunk, encoding); |
| } |
| |
| if (!chunk || !chunk.length) { |
| return done(); |
| } |
| |
| this.inputBytes += chunk.length; |
| |
| if (this._remainingBytes && this._remainingBytes.length) { |
| chunk = Buffer.concat([this._remainingBytes, chunk]); |
| this._remainingBytes = false; |
| } |
| |
| if (chunk.length % 3) { |
| this._remainingBytes = chunk.slice(chunk.length - chunk.length % 3); |
| chunk = chunk.slice(0, chunk.length - chunk.length % 3); |
| } else { |
| this._remainingBytes = false; |
| } |
| |
| b64 = this._curLine + encode(chunk); |
| |
| if (this.options.lineLength) { |
| b64 = wrap(b64, this.options.lineLength); |
| b64 = b64.replace(/(^|\n)([^\n]*)$/, (match, lineBreak, lastLine) => { |
| this._curLine = lastLine; |
| return lineBreak; |
| }); |
| } |
| |
| if (b64) { |
| this.outputBytes += b64.length; |
| this.push(b64); |
| } |
| |
| done(); |
| } |
| |
| _flush(done) { |
| if (this._remainingBytes && this._remainingBytes.length) { |
| this._curLine += encode(this._remainingBytes); |
| } |
| |
| if (this._curLine) { |
| this._curLine = wrap(this._curLine, this.options.lineLength); |
| this.outputBytes += this._curLine.length; |
| this.push(this._curLine, 'ascii'); |
| this._curLine = ''; |
| } |
| done(); |
| } |
| } |
| |
| // expose to the world |
| module.exports = { |
| encode, |
| wrap, |
| Encoder |
| }; |