node-jose-browserify
Version:
An advanced version of Cisco's node-jose module that works in both browser and the server.
475 lines (427 loc) • 12 kB
JavaScript
/*!
* util/databuffer.js - Forge-compatible Buffer based on Node.js Buffers
*
* Copyright (c) 2015 Cisco Systems, Inc. See LICENSE file.
*/
"use strict";
var forge = require("../deps/forge.js"),
base64url = require("./base64url.js");
/**
*
*/
function DataBuffer(b, options) {
options = options || {};
// treat (views of) (Array)Buffers special
// NOTE: default implementation creates copies, but efficiently
// wherever possible
if (Buffer.isBuffer(b)) {
this.data = b;
} else if (forge.util.isArrayBuffer(b)) {
b = new Uint8Array(b);
this.data = Buffer.from(b);
} else if (forge.util.isArrayBufferView(b)) {
b = new Uint8Array(b.buffer, b.byteOffset, b.byteLength);
this.data = Buffer.from(b);
}
if (this.data) {
this.write = this.data.length;
b = undefined;
}
// setup growth rate
this.growSize = options.growSize || DataBuffer.DEFAULT_GROW_SIZE;
// initialize pointers and data
this.write = this.write || 0;
this.read = this.read || 0;
if (b) {
this.putBytes(b);
} else if (!this.data) {
this.accommodate(0);
}
// massage read/write pointers
options.readOffset = ("readOffset" in options) ?
options.readOffset :
this.read;
this.write = ("writeOffset" in options) ?
options.writeOffset :
this.write;
this.read = Math.min(options.readOffset, this.write);
}
DataBuffer.DEFAULT_GROW_SIZE = 16;
DataBuffer.prototype.length = function() {
return this.write - this.read;
};
DataBuffer.prototype.available = function() {
return this.data.length - this.write;
};
DataBuffer.prototype.isEmpty = function() {
return this.length() <= 0;
};
DataBuffer.prototype.accommodate = function(length) {
if (!this.data) {
// initializes a new buffer
length = Math.max(this.write + length, this.growSize);
this.data = Buffer.alloc(length);
} else if (this.available() < length) {
length = Math.max(length, this.growSize);
// create a new empty buffer, and copy current one into it
var src = this.data;
var dst = Buffer.alloc(src.length + length);
src.copy(dst, 0);
// set data as the new buffer
this.data = dst;
}
// ensure the rest is 0
this.data.fill(0, this.write);
return this;
};
DataBuffer.prototype.clear = function() {
this.read = this.write = 0;
this.data = Buffer.alloc(0);
return this;
};
DataBuffer.prototype.truncate = function(count) {
// chop off <count> bytes from the end
this.write = this.read + Math.max(0, this.length() - count);
// ensure the remainder is 0
this.data.fill(0, this.write);
return this;
};
DataBuffer.prototype.compact = function() {
if (this.read > 0) {
if (this.write === this.read) {
this.read = this.write = 0;
} else {
this.data.copy(this.data, 0, this.read, this.write);
this.write = this.write - this.read;
this.read = 0;
}
// ensure remainder is 0
this.data.fill(0, this.write);
}
return this;
};
DataBuffer.prototype.copy = function() {
return new DataBuffer(this, {
readOffset: this.read,
writeOffset: this.write,
growSize: this.growSize
});
};
DataBuffer.prototype.equals = function(test) {
if (!DataBuffer.isBuffer(test)) {
return false;
}
if (test.length() !== this.length()) {
return false;
}
var rval = true,
delta = this.read - test.read;
// constant time
for (var idx = test.read; test.write > idx; idx++) {
rval = rval && (this.data[idx + delta] === test.data[idx]);
}
return rval;
};
DataBuffer.prototype.at = function(idx) {
return this.data[this.read + idx];
};
DataBuffer.prototype.setAt = function(idx, b) {
this.data[this.read + idx] = b;
return this;
};
DataBuffer.prototype.last = function() {
return this.data[this.write - 1];
};
DataBuffer.prototype.bytes = function(count) {
var rval;
if (undefined === count) {
count = this.length();
} else if (count) {
count = Math.min(count, this.length());
}
if (0 === count) {
rval = "";
} else {
var begin = this.read,
end = begin + count,
data = this.data.slice(begin, end);
rval = String.fromCharCode.apply(null, data);
}
return rval;
};
DataBuffer.prototype.buffer = function(count) {
var rval;
if (undefined === count) {
count = this.length();
} else if (count) {
count = Math.min(count, this.length());
}
if (0 === count) {
rval = new ArrayBuffer(0);
} else {
var begin = this.read,
end = begin + count,
data = this.data.slice(begin, end);
rval = new Uint8Array(end - begin);
rval.set(data);
}
return rval;
};
DataBuffer.prototype.native = function(count) {
var rval;
if ("undefined" === typeof count) {
count = this.length();
} else if (count) {
count = Math.min(count, this.length());
}
if (0 === count) {
rval = Buffer.alloc(0);
} else {
var begin = this.read,
end = begin + count;
rval = this.data.slice(begin, end);
}
return rval;
};
DataBuffer.prototype.toHex = function() {
return this.toString("hex");
};
DataBuffer.prototype.toString = function(encoding) {
// short circuit empty string
if (0 === this.length()) {
return "";
}
var view = this.data.slice(this.read, this.write);
encoding = encoding || "utf8";
// special cases, then built-in support
switch (encoding) {
case "raw":
return view.toString("binary");
case "base64url":
return base64url.encode(view);
case "utf16":
return view.toString("ucs2");
default:
return view.toString(encoding);
}
};
DataBuffer.prototype.fillWithByte = function(b, n) {
if (!n) {
n = this.available();
}
this.accommodate(n);
this.data.fill(b, this.write, this.write + n);
this.write += n;
return this;
};
DataBuffer.prototype.getBuffer = function(count) {
var rval = this.buffer(count);
this.read += rval.byteLength;
return rval;
};
DataBuffer.prototype.putBuffer = function(bytes) {
return this.putBytes(bytes);
};
DataBuffer.prototype.getBytes = function(count) {
var rval = this.bytes(count);
this.read += rval.length;
return rval;
};
DataBuffer.prototype.putBytes = function(bytes, encoding) {
if ("string" === typeof bytes) {
// fixup encoding
encoding = encoding || "binary";
switch (encoding) {
case "utf16":
// treat as UCS-2/UTF-16BE
encoding = "ucs-2";
break;
case "raw":
encoding = "binary";
break;
case "base64url":
// NOTE: this returns a Buffer
bytes = base64url.decode(bytes);
break;
}
// replace bytes with decoded Buffer (if not already)
if (!Buffer.isBuffer(bytes)) {
bytes = Buffer.from(bytes, encoding);
}
}
var src, dst;
if (bytes instanceof DataBuffer) {
// be slightly more efficient
var orig = bytes;
bytes = orig.data.slice(orig.read, orig.write);
orig.read = orig.write;
} else if (bytes instanceof forge.util.ByteStringBuffer) {
bytes = bytes.getBytes();
}
// process array
if (Buffer.isBuffer(bytes)) {
src = bytes;
} else if (Array.isArray(bytes)) {
src = Buffer.from(bytes);
} else if (forge.util.isArrayBuffer(bytes)) {
src = new Uint8Array(bytes);
src = Buffer.from(src);
} else if (forge.util.isArrayBufferView(bytes)) {
src = (bytes instanceof Uint8Array) ?
bytes :
new Uint8Array(bytes.buffer,
bytes.byteOffset,
bytes.byteLength);
src = Buffer.from(src);
} else {
throw new TypeError("invalid source type");
}
this.accommodate(src.length);
dst = this.data;
src.copy(dst, this.write);
this.write += src.length;
return this;
};
DataBuffer.prototype.getNative = function(count) {
var rval = this.native(count);
this.read += rval.length;
return rval;
};
DataBuffer.prototype.putNative = DataBuffer.prototype.putBuffer;
DataBuffer.prototype.getByte = function() {
var b = this.data[this.read];
this.read = Math.min(this.read + 1, this.write);
return b;
};
DataBuffer.prototype.putByte = function(b) {
this.accommodate(1);
this.data[this.write] = b & 0xff;
this.write++;
return this;
};
DataBuffer.prototype.getInt16 = function() {
var n = (this.data[this.read] << 8) ^
(this.data[this.read + 1]);
this.read = Math.min(this.read + 2, this.write);
return n;
};
DataBuffer.prototype.putInt16 = function(n) {
this.accommodate(2);
this.data[this.write] = (n >>> 8) & 0xff;
this.data[this.write + 1] = n & 0xff;
this.write += 2;
return this;
};
DataBuffer.prototype.getInt24 = function() {
var n = (this.data[this.read] << 16) ^
(this.data[this.read + 1] << 8) ^
this.data[this.read + 2];
this.read = Math.min(this.read + 3, this.write);
return n;
};
DataBuffer.prototype.putInt24 = function(n) {
this.accommodate(3);
this.data[this.write] = (n >>> 16) & 0xff;
this.data[this.write + 1] = (n >>> 8) & 0xff;
this.data[this.write + 2] = n & 0xff;
this.write += 3;
return this;
};
DataBuffer.prototype.getInt32 = function() {
var n = (this.data[this.read] << 24) ^
(this.data[this.read + 1] << 16) ^
(this.data[this.read + 2] << 8) ^
this.data[this.read + 3];
this.read = Math.min(this.read + 4, this.write);
return n;
};
DataBuffer.prototype.putInt32 = function(n) {
this.accommodate(4);
this.data[this.write] = (n >>> 24) & 0xff;
this.data[this.write + 1] = (n >>> 16) & 0xff;
this.data[this.write + 2] = (n >>> 8) & 0xff;
this.data[this.write + 3] = n & 0xff;
this.write += 4;
return this;
};
DataBuffer.prototype.getInt16Le = function() {
var n = (this.data[this.read + 1] << 8) ^
this.data[this.read];
this.read = Math.min(this.read + 2, this.write);
return n;
};
DataBuffer.prototype.putInt16Le = function(n) {
this.accommodate(2);
this.data[this.write + 1] = (n >>> 8) & 0xff;
this.data[this.write] = n & 0xff;
this.write += 2;
return this;
};
DataBuffer.prototype.getInt24Le = function() {
var n = (this.data[this.read + 2] << 16) ^
(this.data[this.read + 1] << 8) ^
this.data[this.read];
this.read = Math.min(this.read + 3, this.write);
return n;
};
DataBuffer.prototype.putInt24Le = function(n) {
this.accommodate(3);
this.data[this.write + 2] = (n >>> 16) & 0xff;
this.data[this.write + 1] = (n >>> 8) & 0xff;
this.data[this.write] = n & 0xff;
this.write += 3;
return this;
};
DataBuffer.prototype.getInt32Le = function() {
var n = (this.data[this.read + 3] << 24) ^
(this.data[this.read + 2] << 16) ^
(this.data[this.read + 1] << 8) ^
this.data[this.read];
this.read = Math.min(this.read + 4, this.write);
return n;
};
DataBuffer.prototype.putInt32Le = function(n) {
this.accommodate(4);
this.data[this.write + 3] = (n >>> 24) & 0xff;
this.data[this.write + 2] = (n >>> 16) & 0xff;
this.data[this.write + 1] = (n >>> 8) & 0xff;
this.data[this.write] = n & 0xff;
this.write += 4;
return this;
};
DataBuffer.prototype.getInt = function(bits) {
var rval = 0;
do {
rval = (rval << 8) | this.getByte();
bits -= 8;
} while (bits > 0);
return rval;
};
DataBuffer.prototype.putInt = function(n, bits) {
this.accommodate(Math.ceil(bits / 8));
do {
bits -= 8;
this.putByte((n >> bits) & 0xff);
} while (bits > 0);
return this;
};
DataBuffer.prototype.putSignedInt = function(n, bits) {
if (n < 0) {
n += 2 << (bits - 1);
}
return this.putInt(n, bits);
};
DataBuffer.prototype.putString = function(str) {
return this.putBytes(str, "utf16");
};
DataBuffer.isBuffer = function(test) {
return (test instanceof DataBuffer);
};
DataBuffer.asBuffer = function(orig) {
return DataBuffer.isBuffer(orig) ?
orig :
orig ?
new DataBuffer(orig) :
new DataBuffer();
};
module.exports = forge.util.ByteBuffer = DataBuffer;