// This file was copied from https://github.com/substack/node-bufferlist // and modified to be able to copy bytes from the bufferlist directly into // a pre-existing fixed-size buffer without an additional memory allocation. // bufferlist.js // Treat a linked list of buffers as a single variable-size buffer. var Buffer = require('buffer').Buffer; var EventEmitter = require('events').EventEmitter; var bufferAllocUnsafe = require('../lib/utils').bufferAllocUnsafe; module.exports = BufferList; module.exports.BufferList = BufferList; // backwards compatibility function BufferList(opts) { if (!(this instanceof BufferList)) return new BufferList(opts); EventEmitter.call(this); var self = this; if (typeof(opts) == 'undefined') opts = {}; // default encoding to use for take(). Leaving as 'undefined' // makes take() return a Buffer instead. self.encoding = opts.encoding; var head = { next : null, buffer : null }; var last = { next : null, buffer : null }; // length can get negative when advanced past the end // and this is the desired behavior var length = 0; self.__defineGetter__('length', function () { return length; }); // keep an offset of the head to decide when to head = head.next var offset = 0; // Write to the bufferlist. Emits 'write'. Always returns true. self.write = function (buf) { if (!head.buffer) { head.buffer = buf; last = head; } else { last.next = { next : null, buffer : buf }; last = last.next; } length += buf.length; self.emit('write', buf); return true; }; self.end = function (buf) { if (Buffer.isBuffer(buf)) self.write(buf); }; // Push buffers to the end of the linked list. (deprecated) // Return this (self). self.push = function () { var args = [].concat.apply([], arguments); args.forEach(self.write); return self; }; // For each buffer, perform some action. // If fn's result is a true value, cut out early. // Returns this (self). self.forEach = function (fn) { if (!head.buffer) return bufferAllocUnsafe(0); if (head.buffer.length - offset <= 0) return self; var firstBuf = head.buffer.slice(offset); var b = { buffer : firstBuf, next : head.next }; while (b && b.buffer) { var r = fn(b.buffer); if (r) break; b = b.next; } return self; }; // Create a single Buffer out of all the chunks or some subset specified by // start and one-past the end (like slice) in bytes. self.join = function (start, end) { if (!head.buffer) return bufferAllocUnsafe(0); if (start == undefined) start = 0; if (end == undefined) end = self.length; var big = bufferAllocUnsafe(end - start); var ix = 0; self.forEach(function (buffer) { if (start < (ix + buffer.length) && ix < end) { // at least partially contained in the range buffer.copy( big, Math.max(0, ix - start), Math.max(0, start - ix), Math.min(buffer.length, end - ix) ); } ix += buffer.length; if (ix > end) return true; // stop processing past end }); return big; }; self.joinInto = function (targetBuffer, targetStart, sourceStart, sourceEnd) { if (!head.buffer) return new bufferAllocUnsafe(0); if (sourceStart == undefined) sourceStart = 0; if (sourceEnd == undefined) sourceEnd = self.length; var big = targetBuffer; if (big.length - targetStart < sourceEnd - sourceStart) { throw new Error("Insufficient space available in target Buffer."); } var ix = 0; self.forEach(function (buffer) { if (sourceStart < (ix + buffer.length) && ix < sourceEnd) { // at least partially contained in the range buffer.copy( big, Math.max(targetStart, targetStart + ix - sourceStart), Math.max(0, sourceStart - ix), Math.min(buffer.length, sourceEnd - ix) ); } ix += buffer.length; if (ix > sourceEnd) return true; // stop processing past end }); return big; }; // Advance the buffer stream by n bytes. // If n the aggregate advance offset passes the end of the buffer list, // operations such as .take() will return empty strings until enough data is // pushed. // Returns this (self). self.advance = function (n) { offset += n; length -= n; while (head.buffer && offset >= head.buffer.length) { offset -= head.buffer.length; head = head.next ? head.next : { buffer : null, next : null } ; } if (head.buffer === null) last = { next : null, buffer : null }; self.emit('advance', n); return self; }; // Take n bytes from the start of the buffers. // Returns a string. // If there are less than n bytes in all the buffers or n is undefined, // returns the entire concatenated buffer string. self.take = function (n, encoding) { if (n == undefined) n = self.length; else if (typeof n !== 'number') { encoding = n; n = self.length; } var b = head; if (!encoding) encoding = self.encoding; if (encoding) { var acc = ''; self.forEach(function (buffer) { if (n <= 0) return true; acc += buffer.toString( encoding, 0, Math.min(n,buffer.length) ); n -= buffer.length; }); return acc; } else { // If no 'encoding' is specified, then return a Buffer. return self.join(0, n); } }; // The entire concatenated buffer as a string. self.toString = function () { return self.take('binary'); }; } require('util').inherits(BufferList, EventEmitter);