'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 = Buffer.from(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) { if (encoding !== 'buffer') { chunk = Buffer.from(chunk, encoding); } if (!chunk || !chunk.length) { return setImmediate(done); } this.inputBytes += chunk.length; if (this._remainingBytes && this._remainingBytes.length) { chunk = Buffer.concat([this._remainingBytes, chunk], this._remainingBytes.length + chunk.length); 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; } let b64 = this._curLine + encode(chunk); if (this.options.lineLength) { b64 = wrap(b64, this.options.lineLength); // remove last line as it is still most probably incomplete let lastLF = b64.lastIndexOf('\n'); if (lastLF < 0) { this._curLine = b64; b64 = ''; } else if (lastLF === b64.length - 1) { this._curLine = ''; } else { this._curLine = b64.substr(lastLF + 1); b64 = b64.substr(0, lastLF + 1); } } if (b64) { this.outputBytes += b64.length; this.push(Buffer.from(b64, 'ascii')); } setImmediate(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 };