web-api-manager/lib/pack.js

154 lines
4.7 KiB
JavaScript

/*jslint es6: true, for: true, bitwise: true*/
/*global window*/
// Functions to represent a selection of options out of a bigger set
// in a compact, base64 representation.
//
// This is used in the extension to encode which Web API standards should
// be blocked on the current domain, in a way that can be encoded in a cookie
// value.
//
// The two exposed functions in this module are pack and unpack, which are
// inverses of each other. For example:
//
// const options = ["A", "B", "C"];
// const selection = ["B", "C"];
// const base64EncodedSelection = pack(options, selection);
// const decodedSelection = unpack(options, base64EncodedSelection);
// JSON.stringify(decodedSelection) === JSON.stringify(selection) // true
(function () {
"use strict";
const bucketSize = 8;
const bufferToBase64 = function (buf) {
const binstr = Array.prototype.map.call(buf, function (ch) {
return String.fromCharCode(ch);
}).join('');
return window.btoa(binstr);
};
const base64StrToBuffer = function (base64str) {
const binstr = window.atob(base64str);
const buf = new Uint8Array(binstr.length);
Array.prototype.forEach.call(binstr, function (ch, i) {
buf[i] = ch.charCodeAt(0);
});
return buf;
};
const binOptionsReduceFunction = function (binSize, prev, next) {
if (prev.length === 0) {
prev.push([next]);
return prev;
}
const mostRecentBin = prev[prev.length - 1];
if (mostRecentBin.length < binSize) {
mostRecentBin.push(next);
return prev;
}
prev.push([next]);
return prev;
};
/**
* Encodes a selection of values, from a larger set of possible options,
* into a base64 encoded bitfield.
*
* The caller should make sure that selected is a subset of options.
*
* The ordering of options and selected do not matter.
*
* This function is the inverse of the `unpack` function from this module.
*
* @param array options
* An array of all possible options that might need to be encoded.
* @param array selected
* An array containing zero or more elements from option.
*
* @return string
* A base64 encoded string, which encodes which elements in `options`
* were in the provided `selected` array.
*/
const pack = function (options, selected) {
const numBuckets = Math.ceil(options.length / bucketSize);
const binToBucketSizeFunc = binOptionsReduceFunction.bind(undefined, bucketSize);
options.sort();
const binnedOptions = options.reduce(binToBucketSizeFunc, []);
const bitFields = new Uint8Array(numBuckets);
let i, j;
for (i = 0; i < numBuckets; i += 1) {
let bitfield = 0;
let currentBucket = binnedOptions[i];
for (j = 0; j < currentBucket.length; j += 1) {
let currentOption = currentBucket[j];
if (selected.indexOf(currentOption) !== -1) {
bitfield |= 1 << j;
}
}
bitFields[i] = bitfield;
}
const encodedString = bufferToBase64(bitFields);
return encodedString;
};
/**
* Decodes a base64 encoded bitfield into an array of values, each of
* which are in the provided options array.
*
* The ordering of the options array does not matter.
*
* This function is the inverse of the `pack` function from this module.
*
* @param array options
* An array of all possible options that might need to be encoded.
* @param string data
* A base64 encoded string, generated from the `pack` function in
* this module.
*
* @return array
* An array of zero or more elements, each of which will be in the
* options array.
*/
const unpack = function (options, data) {
const binToBucketSizeFunc = binOptionsReduceFunction.bind(undefined, bucketSize);
options.sort();
const binnedOptions = options.reduce(binToBucketSizeFunc, []);
const bitFields = base64StrToBuffer(data);
const result = [];
let i, j;
for (i = 0; i < bitFields.length; i += 1) {
let currentBitField = bitFields[i];
let currentOptionsBin = binnedOptions[i];
for (j = 0; j < bucketSize; j += 1) {
if (currentBitField & (1 << j)) {
let currentOption = currentOptionsBin[j];
result.push(currentOption);
}
}
}
return result;
};
window.WEB_API_MANAGER.packingLib = {
pack,
unpack
};
}());