From 69c66e7d994c3d18449e9b1f1dc2ce3e16692108 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Wed, 2 Aug 2023 15:08:17 -0500 Subject: [PATCH] Use session storage instead of location.hash --- public/common/base64url.js | 38 --------------------------- public/common/parseHash.js | 41 ----------------------------- public/common/vendor/base64-js.js | 29 +++++++++++++++++++++ public/gif/gif.js | 11 ++++---- public/index/index.js | 20 ++++---------- public/png/png.js | 11 ++++---- test/common/base64url.test.ts | 43 ------------------------------- test/common/parseHash.test.ts | 35 ------------------------- 8 files changed, 44 insertions(+), 184 deletions(-) delete mode 100644 public/common/base64url.js delete mode 100644 public/common/parseHash.js create mode 100644 public/common/vendor/base64-js.js delete mode 100644 test/common/base64url.test.ts delete mode 100644 test/common/parseHash.test.ts diff --git a/public/common/base64url.js b/public/common/base64url.js deleted file mode 100644 index e9c30a6..0000000 --- a/public/common/base64url.js +++ /dev/null @@ -1,38 +0,0 @@ -// @ts-check - -/** - * Convert a `Uint8Array` to a URL-safe base64 string. - * @param {Uint8Array} bytes - * @returns {string} - */ -export const stringify = (bytes) => { - const string = String.fromCharCode(...bytes); - const base64 = btoa(string); - return base64 - .replaceAll("+", "-") - .replaceAll("/", "_") - .replaceAll("=", ""); -}; - -/** - * Convert a URL-safe base64 string to a `Uint8Array`. - * Returns `null` if the string is invalid. - * @param {string} text - * @returns {null | Uint8Array} - */ -export const parse = (text) => { - const normalizedText = text - .replaceAll("-", "+") - .replaceAll("_", "/") - .replaceAll(" ", "+"); - try { - const string = atob(normalizedText); - const bytes = new Uint8Array(string.length); - for (let i = 0; i < string.length; i++) { - bytes[i] = string.charCodeAt(i); - } - return bytes; - } catch (_err) { - return null; - } -}; diff --git a/public/common/parseHash.js b/public/common/parseHash.js deleted file mode 100644 index bc0fc10..0000000 --- a/public/common/parseHash.js +++ /dev/null @@ -1,41 +0,0 @@ -// @ts-check - -import maybeJsonParse from "../common/maybeJsonParse.js"; -import * as base64url from "../common/base64url.js"; - -/** - * Parse a location hash into `name` and `bytes`. - * @param {string} hash - * @returns {null | { name: string, bytes: Uint8Array }} The parsed data, or `null` if the hash can't be parsed. - */ -export default (hash) => { - const normalizedHash = decodeURI(hash.replace(/^#/, "")); - - const parsed = maybeJsonParse(normalizedHash); - if (!parsed || typeof parsed !== "object") { - console.warn("Couldn't parse hash as JSON object"); - return null; - } - - if ( - !("name" in parsed) || (typeof parsed.name !== "string") || - !("bytes" in parsed) || (typeof parsed.bytes !== "string") - ) { - console.warn("Hash fields missing or invalid type"); - return null; - } - - const { name } = parsed; - if (!name) { - console.warn("Name is empty"); - return null; - } - - const bytes = base64url.parse(parsed.bytes); - if (!bytes || !bytes.byteLength) { - console.warn("Bytes is empty"); - return null; - } - - return { name, bytes }; -}; diff --git a/public/common/vendor/base64-js.js b/public/common/vendor/base64-js.js new file mode 100644 index 0000000..37b1702 --- /dev/null +++ b/public/common/vendor/base64-js.js @@ -0,0 +1,29 @@ +/*! base64-js + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Jameson Little + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// deno-lint-ignore-file +// deno-fmt-ignore-file + +var B=Object.create;var l=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var w=Object.getPrototypeOf,j=Object.prototype.hasOwnProperty;var H=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports),U=(r,e)=>{for(var t in e)l(r,t,{get:e[t],enumerable:!0})},A=(r,e,t,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of k(e))!j.call(r,o)&&o!==t&&l(r,o,{get:()=>e[o],enumerable:!(a=_(e,o))||a.enumerable});return r},u=(r,e,t)=>(A(r,e,"default"),t&&A(t,e,"default")),C=(r,e,t)=>(t=r!=null?B(w(r)):{},A(e||!r||!r.__esModule?l(t,"default",{value:r,enumerable:!0}):t,r));var p=H(y=>{"use strict";y.byteLength=I;y.toByteArray=T;y.fromByteArray=D;var h=[],d=[],E=typeof Uint8Array<"u"?Uint8Array:Array,s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";for(F=0,L=s.length;F0)throw new Error("Invalid string. Length must be a multiple of 4");var t=r.indexOf("=");t===-1&&(t=e);var a=t===e?0:4-t%4;return[t,a]}function I(r){var e=g(r),t=e[0],a=e[1];return(t+a)*3/4-a}function O(r,e,t){return(e+t)*3/4-t}function T(r){var e,t=g(r),a=t[0],o=t[1],n=new E(O(r,a,o)),v=0,x=o>0?a-4:a,f;for(f=0;f>16&255,n[v++]=e>>8&255,n[v++]=e&255;return o===2&&(e=d[r.charCodeAt(f)]<<2|d[r.charCodeAt(f+1)]>>4,n[v++]=e&255),o===1&&(e=d[r.charCodeAt(f)]<<10|d[r.charCodeAt(f+1)]<<4|d[r.charCodeAt(f+2)]>>2,n[v++]=e>>8&255,n[v++]=e&255),n}function q(r){return h[r>>18&63]+h[r>>12&63]+h[r>>6&63]+h[r&63]}function z(r,e,t){for(var a,o=[],n=e;nx?x:v+n));return a===1?(e=r[t-1],o.push(h[e>>2]+h[e<<4&63]+"==")):a===2&&(e=(r[t-2]<<8)+r[t-1],o.push(h[e>>10]+h[e>>4&63]+h[e<<2&63]+"=")),o.join("")}});var c={};U(c,{byteLength:()=>G,default:()=>N,fromByteArray:()=>K,toByteArray:()=>J});var i=C(p());u(c,C(p()));var{byteLength:G,toByteArray:J,fromByteArray:K}=i,{default:m,...M}=i,N=m!==void 0?m:M;export{G as byteLength,N as default,K as fromByteArray,J as toByteArray}; \ No newline at end of file diff --git a/public/gif/gif.js b/public/gif/gif.js index 8fa834b..9a9ed74 100644 --- a/public/gif/gif.js +++ b/public/gif/gif.js @@ -1,17 +1,16 @@ // @ts-check -import parseHash from "../common/parseHash.js"; +import * as base64 from "../common/vendor/base64-js.js"; const main = () => { // TODO: We may want a better UI here. - // TODO: Handle hash changes. - const parsedHash = parseHash(location.hash); - if (!parsedHash) { + // TODO: Handle errors. + const fileDataBase64 = window.sessionStorage.getItem("fileData"); + if (!fileDataBase64) { location.href = ".."; return; } - - const { bytes } = parsedHash; + const bytes = base64.toByteArray(fileDataBase64); console.log(bytes); }; diff --git a/public/index/index.js b/public/index/index.js index 410a0bd..658f804 100644 --- a/public/index/index.js +++ b/public/index/index.js @@ -1,8 +1,8 @@ // @ts-check +import * as base64 from "../common/vendor/base64-js.js"; import { SUPPORTED_FILE_TYPES } from "./constants.js"; import crel from "../common/crel.js"; -import * as base64url from "../common/base64url.js"; import routeFile from "./routeFile.js"; const accept = SUPPORTED_FILE_TYPES @@ -51,16 +51,6 @@ const disclaimerParagraphEl = crel( crel("small", {}, "Your files do not leave your computer."), ); -/** - * @param {Blob} blob - * @returns {Promise} - */ -const formatBytes = async (blob) => { - const arrayBuffer = await blob.arrayBuffer(); - const bytes = new Uint8Array(arrayBuffer); - return base64url.stringify(bytes); -}; - const main = () => { const appEl = document.getElementById("app"); if (!appEl) throw new Error("HTML is not set up correctly"); @@ -81,10 +71,10 @@ const main = () => { return; } - location.href = route + "#" + JSON.stringify({ - name: file.name, - bytes: await formatBytes(file), - }); + const fileData = new Uint8Array(await file.arrayBuffer()); + window.sessionStorage.setItem("fileData", base64.fromByteArray(fileData)); + + location.href = route; }); appEl.append(labelParagraphEl, inputContainerEl, disclaimerParagraphEl); diff --git a/public/png/png.js b/public/png/png.js index f784338..7896d4a 100644 --- a/public/png/png.js +++ b/public/png/png.js @@ -1,6 +1,6 @@ // @ts-check -import parseHash from "../common/parseHash.js"; +import * as base64 from "../common/vendor/base64-js.js"; import parsePng from "./parsePng.js"; import getNodeUi from "./getNodeUi.js"; import explorer from "./explorer.js"; @@ -11,14 +11,13 @@ if (!errorEl || !explorerEl) throw new Error("HTML is not set up correctly"); const main = () => { // TODO: We may want a better UI here. - // TODO: Handle hash changes. - const parsedHash = parseHash(location.hash); - if (!parsedHash) { + // TODO: Handle errors. + const fileDataBase64 = window.sessionStorage.getItem("fileData"); + if (!fileDataBase64) { location.href = ".."; return; } - - const { bytes } = parsedHash; + const bytes = base64.toByteArray(fileDataBase64); const rootNode = parsePng(bytes); if (!rootNode) { diff --git a/test/common/base64url.test.ts b/test/common/base64url.test.ts deleted file mode 100644 index c4dcf90..0000000 --- a/test/common/base64url.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { assertEquals } from "assert"; -import { b } from "../helpers.ts"; -import * as base64url from "../../public/common/base64url.js"; - -Deno.test('encodes no bytes as ""', () => { - assertEquals(base64url.stringify(b()), ""); -}); - -Deno.test("encodes in a URL-safe way", () => { - const input = b(105, 183, 62, 249, 215, 191, 254); - assertEquals( - base64url.stringify(input), - "abc--de__g", - ); -}); - -Deno.test("decodes the empty string", () => { - assertEquals(base64url.parse(""), b()); -}); - -Deno.test("decodes URL-safe strings", () => { - assertEquals(base64url.parse("_-o"), b(255, 234)); -}); - -Deno.test("decodes URL-unsafe strings (as a bonus)", () => { - assertEquals(base64url.parse("/+o="), b(255, 234)); -}); - -Deno.test("round-trips", () => { - const testCases = [ - b(), - b(1), - b(1, 2, 3), - b(255, 234), - b(105, 183, 62, 249, 215, 191, 254), - ]; - - for (const original of testCases) { - const string = base64url.stringify(original); - const parsed = base64url.parse(string); - assertEquals(parsed, original, `Round-trip failed for ${original}`); - } -}); diff --git a/test/common/parseHash.test.ts b/test/common/parseHash.test.ts deleted file mode 100644 index 652a31b..0000000 --- a/test/common/parseHash.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { assertEquals } from "assert"; -import { stubbingWarn } from "../helpers.ts"; -import parseHash from "../../public/common/parseHash.js"; - -Deno.test( - "returns null if hash cannot be parsed", - stubbingWarn(() => { - const testCases = [ - // Missing fields - "#null", - "#{}", - "#{%22name%22:%22image.png%22}", - "#{%22bytes%22:%22AQID%22}", - // Invalid JSON - "", - "#{%22name%22:%22small.png%22,%22bytes%22:%22iAQID", - ]; - - for (const testCase of testCases) { - assertEquals( - parseHash(testCase), - null, - `Parsing ${testCase} should fail`, - ); - } - }), -); - -Deno.test("parses hashes", () => { - const hash = "#{%22name%22:%22small.png%22,%22bytes%22:%22AQID%22}"; - assertEquals(parseHash(hash), { - name: "small.png", - bytes: new Uint8Array([1, 2, 3]), - }); -});