Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/core/components/base64_codec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const BASE64_CHARMAP = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';

/**
* Decode a Base64 encoded string.
*
* @param paddedInput Base64 string with padding
* @returns ArrayBuffer with decoded data
*/
export function decode(paddedInput: string): ArrayBuffer {
// Remove up to last two equal signs.
const input = paddedInput.replace(/==?$/, '');

const outputLength = Math.floor((input.length / 4) * 3);

// Prepare output buffer.
const data = new ArrayBuffer(outputLength);
const view = new Uint8Array(data);

let cursor = 0;

/**
* Returns the next integer representation of a sixtet of bytes from the input
* @returns sixtet of bytes
*/
function nextSixtet() {
const char = input.charAt(cursor++);
const index = BASE64_CHARMAP.indexOf(char);

if (index === -1) {
throw new Error(`Illegal character at ${cursor}: ${input.charAt(cursor - 1)}`);
}

return index;
}

for (let i = 0; i < outputLength; i += 3) {
// Obtain four sixtets
const sx1 = nextSixtet();
const sx2 = nextSixtet();
const sx3 = nextSixtet();
const sx4 = nextSixtet();

// Encode them as three octets
const oc1 = ((sx1 & 0b00111111) << 2) | (sx2 >> 4);
const oc2 = ((sx2 & 0b00001111) << 4) | (sx3 >> 2);
const oc3 = ((sx3 & 0b00000011) << 6) | (sx4 >> 0);

view[i] = oc1;
// Skip padding bytes.
if (sx3 != 64) view[i + 1] = oc2;
if (sx4 != 64) view[i + 2] = oc3;
}

return data;
}
4 changes: 0 additions & 4 deletions src/core/components/token_manager.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
/* */
import Config from './config';
import { GrantTokenOutput } from '../flow_interfaces';

export default class {
_config;

Expand Down
3 changes: 2 additions & 1 deletion src/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import CborReader from 'cbor-sync';
import PubNubCore from '../core/pubnub-common';
import Networking from '../networking';
import Cbor from '../cbor/common';
import { decode } from '../core/components/base64_codec';
import { del, get, patch, post, getfile, postfile } from '../networking/modules/web-node';
import { keepAlive, proxy } from '../networking/modules/node';

Expand All @@ -10,7 +11,7 @@ import PubNubFile from '../file/modules/node';

export = class extends PubNubCore {
constructor(setup: any) {
setup.cbor = new Cbor(CborReader.decode, (base64String: string) => Buffer.from(base64String, 'base64'));
setup.cbor = new Cbor((buffer: ArrayBuffer) => CborReader.decode(Buffer.from(buffer)), decode);
setup.networking = new Networking({
keepAlive,
del,
Expand Down
36 changes: 2 additions & 34 deletions src/web/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import CborReader from 'cbor-js';
import PubNubCore from '../core/pubnub-common';
import Networking from '../networking';
import CryptoJS from '../core/components/cryptography/hmac-sha256';
import { decode } from '../core/components/base64_codec';
import Cbor from '../cbor/common';
import { del, get, post, patch, getfile, postfile } from '../networking/modules/web-node';

Expand All @@ -19,38 +19,6 @@ function sendBeacon(url) {
}
}

function base64ToBinary(base64String) {
const parsedWordArray = CryptoJS.enc.Base64.parse(base64String).words;
const arrayBuffer = new ArrayBuffer(parsedWordArray.length * 4);
const view = new Uint8Array(arrayBuffer);
let filledArrayBuffer = null;
let zeroBytesCount = 0;
let byteOffset = 0;

for (let wordIdx = 0; wordIdx < parsedWordArray.length; wordIdx += 1) {
const word = parsedWordArray[wordIdx];
byteOffset = wordIdx * 4;
view[byteOffset] = (word & 0xff000000) >> 24;
view[byteOffset + 1] = (word & 0x00ff0000) >> 16;
view[byteOffset + 2] = (word & 0x0000ff00) >> 8;
view[byteOffset + 3] = word & 0x000000ff;
}

for (let byteIdx = byteOffset + 3; byteIdx >= byteOffset; byteIdx -= 1) {
if (view[byteIdx] === 0 && zeroBytesCount < 3) {
zeroBytesCount += 1;
}
}

if (zeroBytesCount > 0) {
filledArrayBuffer = view.buffer.slice(0, view.byteLength - zeroBytesCount);
} else {
filledArrayBuffer = view.buffer;
}

return filledArrayBuffer;
}

function stringifyBufferKeys(obj) {
const isObject = (value) => value && typeof value === 'object' && value.constructor === Object;
const isString = (value) => typeof value === 'string' || value instanceof String;
Expand Down Expand Up @@ -99,7 +67,7 @@ export default class extends PubNubCore {
getfile,
postfile,
});
setup.cbor = new Cbor((arrayBuffer) => stringifyBufferKeys(CborReader.decode(arrayBuffer)), base64ToBinary);
setup.cbor = new Cbor((arrayBuffer) => stringifyBufferKeys(CborReader.decode(arrayBuffer)), decode);

setup.PubNubFile = PubNubFile;
setup.cryptography = new WebCryptography();
Expand Down
28 changes: 28 additions & 0 deletions test/unit/base64.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { decode } from '../../src/core/components/base64_codec';

import assert from 'assert';

function assertBufferEqual(actual, expected) {
assert.deepStrictEqual(new Uint8Array(actual), Uint8Array.from(expected));
}

describe('base64 codec', () => {
it('should properly handle padding with zero bytes at the end of the data', () => {
const helloWorld = [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33];
const noZeroBytesResult = decode('SGVsbG8gd29ybGQh');
const oneZeroBytesResult = decode('SGVsbG8gd29ybGQhAA==');
const twoZeroBytesResult = decode('SGVsbG8gd29ybGQhAAA=');
const threeZeroBytesResult = decode('SGVsbG8gd29ybGQhAAAA');

assertBufferEqual(noZeroBytesResult, helloWorld);
assertBufferEqual(oneZeroBytesResult, [...helloWorld, 0]);
assertBufferEqual(twoZeroBytesResult, [...helloWorld, 0, 0]);
assertBufferEqual(threeZeroBytesResult, [...helloWorld, 0, 0, 0]);
});

it('should throw when illegal characters are encountered', () => {
assert.throws(() => {
decode('SGVsbG8g-d29ybGQhAA==');
});
});
});