117 lines
3.6 KiB
JavaScript
117 lines
3.6 KiB
JavaScript
const { Transform } = require('stream');
|
|
|
|
/**
|
|
* Transforms a Buffer stream of binary data to a stream of Base64 text. Note that this will
|
|
* also work on a stream of pure strings, as the Writeable base class will automatically decode
|
|
* text string chunks into Buffers.
|
|
* You can pass optionally a line length or a prefix
|
|
* @extends Transform
|
|
*/
|
|
module.exports = class Base64Encode extends Transform {
|
|
/**
|
|
* Creates a Base64Encode
|
|
* @param {Object=} options - Options for stream creation. Passed to Transform constructor as-is.
|
|
* @param {string=} options.inputEncoding - The input chunk format. Default is 'utf8'. No effect on Buffer input chunks.
|
|
* @param {string=} options.outputEncoding - The output chunk format. Default is 'utf8'. Pass `null` for Buffer chunks.
|
|
* @param {number=} options.lineLength - The max line-length of the output stream.
|
|
* @param {string=} options.prefix - Prefix for output string.
|
|
*/
|
|
constructor(options) {
|
|
super(options);
|
|
|
|
// Any extra chars from the last chunk
|
|
this.extra = null;
|
|
this.lineLength = options && options.lineLength;
|
|
this.currLineLength = 0;
|
|
if (options && options.prefix) {
|
|
this.push(options.prefix);
|
|
}
|
|
|
|
// Default string input to be treated as 'utf8'
|
|
const encIn = options && options.inputEncoding;
|
|
this.setDefaultEncoding(encIn || 'utf8');
|
|
|
|
// Default output to be strings
|
|
const encOut = options && options.outputEncoding;
|
|
if (encOut !== null) {
|
|
this.setEncoding(encOut || 'utf8');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds \r\n as needed to the data chunk to ensure that the output Base64 string meets
|
|
* the maximum line length requirement.
|
|
* @param {string} chunk
|
|
* @returns {string}
|
|
* @private
|
|
*/
|
|
_fixLineLength(chunk) {
|
|
// If we care about line length, add line breaks
|
|
if (!this.lineLength) {
|
|
return chunk;
|
|
}
|
|
|
|
const size = chunk.length;
|
|
const needed = this.lineLength - this.currLineLength;
|
|
let start, end;
|
|
|
|
let _chunk = '';
|
|
for (start = 0, end = needed; end < size; start = end, end += this.lineLength) {
|
|
_chunk += chunk.slice(start, end);
|
|
_chunk += '\r\n';
|
|
}
|
|
|
|
const left = chunk.slice(start);
|
|
this.currLineLength = left.length;
|
|
|
|
_chunk += left;
|
|
|
|
return _chunk;
|
|
}
|
|
|
|
/**
|
|
* Transforms a Buffer chunk of data to a Base64 string chunk.
|
|
* @param {Buffer} chunk
|
|
* @param {string} encoding - unused since chunk is always a Buffer
|
|
* @param cb
|
|
* @private
|
|
*/
|
|
_transform(chunk, encoding, cb) {
|
|
// Add any previous extra bytes to the chunk
|
|
if (this.extra) {
|
|
chunk = Buffer.concat([this.extra, chunk]);
|
|
this.extra = null;
|
|
}
|
|
|
|
// 3 bytes are represented by 4 characters, so we can only encode in groups of 3 bytes
|
|
const remaining = chunk.length % 3;
|
|
|
|
if (remaining !== 0) {
|
|
// Store the extra bytes for later
|
|
this.extra = chunk.slice(chunk.length - remaining);
|
|
chunk = chunk.slice(0, chunk.length - remaining);
|
|
}
|
|
|
|
// Convert chunk to a base 64 string
|
|
chunk = chunk.toString('base64');
|
|
|
|
// Push the chunk
|
|
this.push(Buffer.from(this._fixLineLength(chunk)));
|
|
cb();
|
|
}
|
|
|
|
/**
|
|
* Emits 0 or 4 extra characters of Base64 data.
|
|
* @param cb
|
|
* @private
|
|
*/
|
|
_flush(cb) {
|
|
if (this.extra) {
|
|
this.push(Buffer.from(this._fixLineLength(this.extra.toString('base64'))));
|
|
}
|
|
|
|
cb();
|
|
}
|
|
|
|
};
|