Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Internalize base64-js, convert to TypeScript #2826

Merged
merged 3 commits into from
Aug 28, 2019
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
9 changes: 1 addition & 8 deletions cli/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ if (is_posix) {

ts_sources = [
"../js/assets.ts",
"../js/base64.ts",
"../js/blob.ts",
"../js/body.ts",
"../js/buffer.ts",
Expand Down Expand Up @@ -265,11 +266,3 @@ snapshot("snapshot_compiler") {
":compiler_bundle",
]
}

action("write_gn_args") {
script = "//tools/write_gn_args.py"
outputs = [
"$target_gen_dir/gn_args.txt",
]
args = [ rebase_path(outputs[0], root_build_dir) ]
}
149 changes: 149 additions & 0 deletions js/base64.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Forked from https://github.com/beatgammit/base64-js
// Copyright (c) 2014 Jameson Little. MIT License.

const lookup: string[] = [];
const revLookup: number[] = [];

const code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
for (var i = 0, len = code.length; i < len; ++i) {
lookup[i] = code[i];
revLookup[code.charCodeAt(i)] = i;
}

// Support decoding URL-safe base64 strings, as Node.js does.
// See: https://en.wikipedia.org/wiki/Base64#URL_applications
revLookup["-".charCodeAt(0)] = 62;
revLookup["_".charCodeAt(0)] = 63;

function getLens(b64: string): [number, number] {
var len = b64.length;

if (len % 4 > 0) {
throw new Error("Invalid string. Length must be a multiple of 4");
}

// Trim off extra bytes after placeholder bytes are found
// See: https://github.com/beatgammit/base64-js/issues/42
var validLen = b64.indexOf("=");
if (validLen === -1) validLen = len;

var placeHoldersLen = validLen === len ? 0 : 4 - (validLen % 4);

return [validLen, placeHoldersLen];
}

// base64 is 4/3 + up to two characters of the original data
export function byteLength(b64: string): number {
var lens = getLens(b64);
var validLen = lens[0];
var placeHoldersLen = lens[1];
return ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen;
}

function _byteLength(
b64: string,
validLen: number,
placeHoldersLen: number
): number {
return ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen;
}

export function toByteArray(b64: string): Uint8Array {
var tmp;
var lens = getLens(b64);
var validLen = lens[0];
var placeHoldersLen = lens[1];

var arr = new Uint8Array(_byteLength(b64, validLen, placeHoldersLen));

var curByte = 0;

// if there are placeholders, only get up to the last complete 4 chars
var len = placeHoldersLen > 0 ? validLen - 4 : validLen;

for (var i = 0; i < len; i += 4) {
tmp =
(revLookup[b64.charCodeAt(i)] << 18) |
(revLookup[b64.charCodeAt(i + 1)] << 12) |
(revLookup[b64.charCodeAt(i + 2)] << 6) |
revLookup[b64.charCodeAt(i + 3)];
arr[curByte++] = (tmp >> 16) & 0xff;
arr[curByte++] = (tmp >> 8) & 0xff;
arr[curByte++] = tmp & 0xff;
}

if (placeHoldersLen === 2) {
tmp =
(revLookup[b64.charCodeAt(i)] << 2) |
(revLookup[b64.charCodeAt(i + 1)] >> 4);
arr[curByte++] = tmp & 0xff;
}

if (placeHoldersLen === 1) {
tmp =
(revLookup[b64.charCodeAt(i)] << 10) |
(revLookup[b64.charCodeAt(i + 1)] << 4) |
(revLookup[b64.charCodeAt(i + 2)] >> 2);
arr[curByte++] = (tmp >> 8) & 0xff;
arr[curByte++] = tmp & 0xff;
}

return arr;
}

function tripletToBase64(num: number): string {
return (
lookup[(num >> 18) & 0x3f] +
lookup[(num >> 12) & 0x3f] +
lookup[(num >> 6) & 0x3f] +
lookup[num & 0x3f]
);
}

function encodeChunk(uint8: Uint8Array, start: number, end: number): string {
var tmp;
var output = [];
for (var i = start; i < end; i += 3) {
tmp =
((uint8[i] << 16) & 0xff0000) +
((uint8[i + 1] << 8) & 0xff00) +
(uint8[i + 2] & 0xff);
output.push(tripletToBase64(tmp));
}
return output.join("");
}

export function fromByteArray(uint8: Uint8Array): string {
var tmp;
var len = uint8.length;
var extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes
var parts = [];
var maxChunkLength = 16383; // must be multiple of 3

// go through the array every three bytes, we'll deal with trailing stuff later
for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
parts.push(
encodeChunk(
uint8,
i,
i + maxChunkLength > len2 ? len2 : i + maxChunkLength
)
);
}

// pad the end with zeros, but make sure to not forget the extra bytes
if (extraBytes === 1) {
tmp = uint8[len - 1];
parts.push(lookup[tmp >> 2] + lookup[(tmp << 4) & 0x3f] + "==");
} else if (extraBytes === 2) {
tmp = (uint8[len - 2] << 8) + uint8[len - 1];
parts.push(
lookup[tmp >> 10] +
lookup[(tmp >> 4) & 0x3f] +
lookup[(tmp << 2) & 0x3f] +
"="
);
}

return parts.join("");
}
78 changes: 1 addition & 77 deletions js/body.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,17 @@
import * as streams from "@stardazed/streams";
import * as formData from "./form_data";
import * as blob from "./blob";
import * as encoding from "./text_encoding";
import * as headers from "./headers";

import * as domTypes from "./dom_types";

const { Headers } = headers;

// only namespace imports work for now, plucking out what we need
const { ReadableStream } = streams;
const { FormData } = formData;
const { TextEncoder, TextDecoder } = encoding;
const Blob = blob.DenoBlob;
const DenoBlob = blob.DenoBlob;

type ReadableStreamReader = domTypes.ReadableStreamReader;

interface ReadableStreamController {
enqueue(chunk: string | ArrayBuffer): void;
close(): void;
}

export type BodySource =
| domTypes.Blob
| domTypes.BufferSource
Expand All @@ -47,8 +37,6 @@ function validateBodyType(owner: Body, bodySource: BodySource): boolean {
return true;
} else if (typeof bodySource === "string") {
return true;
} else if (bodySource instanceof ReadableStream) {
return true;
} else if (bodySource instanceof FormData) {
return true;
} else if (!bodySource) {
Expand All @@ -59,58 +47,6 @@ function validateBodyType(owner: Body, bodySource: BodySource): boolean {
);
}

function concatenate(...arrays: Uint8Array[]): ArrayBuffer {
let totalLength = 0;
for (const arr of arrays) {
totalLength += arr.length;
}
const result = new Uint8Array(totalLength);
let offset = 0;
for (const arr of arrays) {
result.set(arr, offset);
offset += arr.length;
}
return result.buffer as ArrayBuffer;
}

function bufferFromStream(stream: ReadableStreamReader): Promise<ArrayBuffer> {
return new Promise(
(resolve, reject): void => {
const parts: Uint8Array[] = [];
const encoder = new TextEncoder();
// recurse
(function pump(): void {
stream
.read()
.then(
({ done, value }): void => {
if (done) {
return resolve(concatenate(...parts));
}

if (typeof value === "string") {
parts.push(encoder.encode(value));
} else if (value instanceof ArrayBuffer) {
parts.push(new Uint8Array(value));
} else if (!value) {
// noop for undefined
} else {
reject("unhandled type on stream read");
}

return pump();
}
)
.catch(
(err): void => {
reject(err);
}
);
})();
}
);
}

function getHeaderValueParams(value: string): Map<string, string> {
const params = new Map();
// Forced to do so for some Map constructor param mismatch
Expand Down Expand Up @@ -145,17 +81,8 @@ export class Body implements domTypes.Body {
if (this._stream) {
return this._stream;
}
if (this._bodySource instanceof ReadableStream) {
// @ts-ignore
this._stream = this._bodySource;
}
if (typeof this._bodySource === "string") {
this._stream = new ReadableStream({
start(controller: ReadableStreamController): void {
controller.enqueue(this._bodySource);
controller.close();
}
});
throw Error("not implemented");
}
return this._stream;
}
Expand Down Expand Up @@ -332,9 +259,6 @@ export class Body implements domTypes.Body {
} else if (typeof this._bodySource === "string") {
const enc = new TextEncoder();
return enc.encode(this._bodySource).buffer as ArrayBuffer;
} else if (this._bodySource instanceof ReadableStream) {
// @ts-ignore
return bufferFromStream(this._bodySource.getReader());
} else if (this._bodySource instanceof FormData) {
const enc = new TextEncoder();
return enc.encode(this._bodySource.toString()).buffer as ArrayBuffer;
Expand Down
9 changes: 0 additions & 9 deletions js/request.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
import * as headers from "./headers";
import * as body from "./body";

import * as streams from "@stardazed/streams";

import * as domTypes from "./dom_types";

const { Headers } = headers;
const { ReadableStream } = streams;

function byteUpperCase(s: string): string {
return String(s).replace(/[a-z]/g, function byteUpperCaseReplace(c): string {
Expand Down Expand Up @@ -144,11 +140,6 @@ export class Request extends body.Body implements domTypes.Request {

let body2 = this._bodySource;

if (this._bodySource instanceof ReadableStream) {
const tees = (this._bodySource as domTypes.ReadableStream).tee();
this._stream = this._bodySource = tees[0];
body2 = tees[1];
}
const cloned = new Request(this.url, {
body: body2,
method: this.method,
Expand Down
34 changes: 1 addition & 33 deletions js/request_test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
import { test, assertEquals, assert } from "./test_util.ts";
import { test, assertEquals } from "./test_util.ts";

test(function fromInit(): void {
const req = new Request("https://example.com", {
Expand All @@ -15,35 +15,3 @@ test(function fromInit(): void {
assertEquals(req.url, "https://example.com");
assertEquals(req.headers.get("test-header"), "value");
});

test(function fromRequest(): void {
const r = new Request("https://example.com");
// @ts-ignore
r._bodySource = "ahoyhoy";
r.headers.set("test-header", "value");

const req = new Request(r);

// @ts-ignore
assertEquals(req._bodySource, r._bodySource);
assertEquals(req.url, r.url);
assertEquals(req.headers.get("test-header"), r.headers.get("test-header"));
});

test(async function cloneRequestBodyStream(): Promise<void> {
// hack to get a stream
const stream = new Request("", { body: "a test body" }).body;
const r1 = new Request("https://example.com", {
body: stream
});

const r2 = r1.clone();

const b1 = await r1.text();
const b2 = await r2.text();

assertEquals(b1, b2);

// @ts-ignore
assert(r1._bodySource !== r2._bodySource);
});
2 changes: 1 addition & 1 deletion js/text_encoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

import * as base64 from "base64-js";
import * as base64 from "./base64";
import * as domTypes from "./dom_types";
import { DenoError, ErrorKind } from "./errors";

Expand Down
3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
{
"name": "deno",
"devDependencies": {
"@stardazed/streams": "3.0.0",
"@types/base64-js": "1.2.5",
"@types/prettier": "1.16.1",
"@typescript-eslint/eslint-plugin": "1.6.0",
"@typescript-eslint/parser": "1.6.0",
"base64-js": "1.3.0",
"eslint": "5.15.1",
"eslint-config-prettier": "4.1.0",
"magic-string": "0.25.2",
Expand Down