2017-10-17 20:17:46 +00:00
|
|
|
// 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);
|
2017-10-17 20:21:42 +00:00
|
|
|
// JSON.stringify(decodedSelection) === JSON.stringify(selection) // true
|
2017-10-13 22:30:57 +00:00
|
|
|
(function () {
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
const bucketSize = 8;
|
|
|
|
|
2017-10-23 08:29:59 +00:00
|
|
|
/**
|
|
|
|
* Encodes a buffer (such as a Uint8Array) to a base64 encoded string.
|
|
|
|
*
|
|
|
|
* @param {ArrayBuffer} buf
|
|
|
|
* A buffer of binary data.
|
|
|
|
*
|
|
|
|
* @return {string}
|
|
|
|
* A base64 encoded string.
|
|
|
|
*/
|
2017-10-14 04:23:49 +00:00
|
|
|
const bufferToBase64 = function (buf) {
|
|
|
|
const binstr = Array.prototype.map.call(buf, function (ch) {
|
|
|
|
return String.fromCharCode(ch);
|
2017-10-23 08:29:59 +00:00
|
|
|
}).join("");
|
2017-10-14 21:16:24 +00:00
|
|
|
return window.btoa(binstr);
|
2017-10-14 04:23:49 +00:00
|
|
|
};
|
|
|
|
|
2017-10-14 21:16:24 +00:00
|
|
|
const base64StrToBuffer = function (base64str) {
|
|
|
|
const binstr = window.atob(base64str);
|
2017-10-14 04:23:49 +00:00
|
|
|
const buf = new Uint8Array(binstr.length);
|
|
|
|
Array.prototype.forEach.call(binstr, function (ch, i) {
|
2017-10-14 20:24:18 +00:00
|
|
|
buf[i] = ch.charCodeAt(0);
|
2017-10-14 04:23:49 +00:00
|
|
|
});
|
|
|
|
return buf;
|
|
|
|
};
|
|
|
|
|
2017-10-13 22:30:57 +00:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2017-10-17 20:17:46 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*
|
2017-10-23 08:29:59 +00:00
|
|
|
* @param {array} options
|
2017-10-17 20:17:46 +00:00
|
|
|
* An array of all possible options that might need to be encoded.
|
2017-10-23 08:29:59 +00:00
|
|
|
* @param {array} selected
|
2017-10-17 20:17:46 +00:00
|
|
|
* An array containing zero or more elements from option.
|
|
|
|
*
|
2017-10-23 08:29:59 +00:00
|
|
|
* @return {string}
|
2017-10-17 20:17:46 +00:00
|
|
|
* A base64 encoded string, which encodes which elements in `options`
|
|
|
|
* were in the provided `selected` array.
|
|
|
|
*/
|
2017-10-13 22:30:57 +00:00
|
|
|
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);
|
|
|
|
|
2017-10-14 20:24:18 +00:00
|
|
|
let i, j;
|
|
|
|
|
|
|
|
for (i = 0; i < numBuckets; i += 1) {
|
2017-10-13 22:30:57 +00:00
|
|
|
let bitfield = 0;
|
2017-10-23 08:29:59 +00:00
|
|
|
const currentBucket = binnedOptions[i];
|
2017-10-13 22:30:57 +00:00
|
|
|
|
2017-10-14 20:24:18 +00:00
|
|
|
for (j = 0; j < currentBucket.length; j += 1) {
|
2017-10-13 22:30:57 +00:00
|
|
|
|
2017-10-23 08:29:59 +00:00
|
|
|
const currentOption = currentBucket[j];
|
2017-10-13 22:30:57 +00:00
|
|
|
if (selected.indexOf(currentOption) !== -1) {
|
|
|
|
bitfield |= 1 << j;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bitFields[i] = bitfield;
|
|
|
|
}
|
|
|
|
|
2017-10-14 04:23:49 +00:00
|
|
|
const encodedString = bufferToBase64(bitFields);
|
|
|
|
return encodedString;
|
2017-10-13 22:30:57 +00:00
|
|
|
};
|
|
|
|
|
2017-10-17 20:17:46 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*
|
2017-10-23 08:29:59 +00:00
|
|
|
* @param {array} options
|
2017-10-17 20:17:46 +00:00
|
|
|
* An array of all possible options that might need to be encoded.
|
2017-10-23 08:29:59 +00:00
|
|
|
* @param {string} data
|
2017-10-17 20:17:46 +00:00
|
|
|
* A base64 encoded string, generated from the `pack` function in
|
|
|
|
* this module.
|
|
|
|
*
|
2017-10-23 08:29:59 +00:00
|
|
|
* @return {array}
|
2017-10-17 20:17:46 +00:00
|
|
|
* An array of zero or more elements, each of which will be in the
|
|
|
|
* options array.
|
|
|
|
*/
|
2017-10-13 22:30:57 +00:00
|
|
|
const unpack = function (options, data) {
|
|
|
|
|
|
|
|
const binToBucketSizeFunc = binOptionsReduceFunction.bind(undefined, bucketSize);
|
|
|
|
options.sort();
|
|
|
|
|
|
|
|
const binnedOptions = options.reduce(binToBucketSizeFunc, []);
|
2017-10-14 21:16:24 +00:00
|
|
|
const bitFields = base64StrToBuffer(data);
|
2017-10-13 22:30:57 +00:00
|
|
|
|
|
|
|
const result = [];
|
|
|
|
|
2017-10-14 20:24:18 +00:00
|
|
|
let i, j;
|
|
|
|
|
|
|
|
for (i = 0; i < bitFields.length; i += 1) {
|
2017-10-23 08:29:59 +00:00
|
|
|
const currentBitField = bitFields[i];
|
|
|
|
const currentOptionsBin = binnedOptions[i];
|
2017-10-13 22:30:57 +00:00
|
|
|
|
2017-10-14 20:24:18 +00:00
|
|
|
for (j = 0; j < bucketSize; j += 1) {
|
2017-10-13 22:30:57 +00:00
|
|
|
if (currentBitField & (1 << j)) {
|
2017-10-23 08:29:59 +00:00
|
|
|
const currentOption = currentOptionsBin[j];
|
2017-10-13 22:30:57 +00:00
|
|
|
result.push(currentOption);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
window.WEB_API_MANAGER.packingLib = {
|
2017-10-14 20:24:18 +00:00
|
|
|
pack,
|
|
|
|
unpack
|
2017-10-13 22:30:57 +00:00
|
|
|
};
|
2017-10-17 20:17:46 +00:00
|
|
|
}());
|