diff --git a/.changeset/huge-zoos-start.md b/.changeset/huge-zoos-start.md new file mode 100644 index 0000000..fcb1c7b --- /dev/null +++ b/.changeset/huge-zoos-start.md @@ -0,0 +1,5 @@ +--- +"@nodesecure/sec-literal": minor +--- + +Remove un-maintained is-base64 package and re-implement the function in the sec-literal project with proper TS definition diff --git a/workspaces/sec-literal/package.json b/workspaces/sec-literal/package.json index aea9bb2..2ff39d9 100644 --- a/workspaces/sec-literal/package.json +++ b/workspaces/sec-literal/package.json @@ -33,11 +33,7 @@ "homepage": "https://github.com/NodeSecure/js-x-ray/tree/master/workspaces/sec-literal#readme", "dependencies": { "frequency-set": "^2.1.0", - "is-base64": "^1.1.0", "is-svg": "^6.0.0", "string-width": "^8.0.0" - }, - "devDependencies": { - "@types/is-base64": "^1.1.3" } } diff --git a/workspaces/sec-literal/src/literal.ts b/workspaces/sec-literal/src/literal.ts index 6e461b2..6e6e128 100644 --- a/workspaces/sec-literal/src/literal.ts +++ b/workspaces/sec-literal/src/literal.ts @@ -1,5 +1,5 @@ -// Import Third-party Dependencies -import isStringBase64 from "is-base64"; +// Import Internal Dependencies +import { isStringBase64 } from "./utils.js"; export type ESTreeLiteral = { type: "Literal"; diff --git a/workspaces/sec-literal/src/utils.ts b/workspaces/sec-literal/src/utils.ts index 4979c4c..2d89842 100644 --- a/workspaces/sec-literal/src/utils.ts +++ b/workspaces/sec-literal/src/utils.ts @@ -5,6 +5,38 @@ import stringWidth from "string-width"; // Import Internal Dependencies import { toValue, type ESTreeLiteral } from "./literal.js"; +export interface IsBase64Options { + allowMime?: boolean; + mimeRequired?: boolean; + paddingRequired?: boolean; + allowEmpty?: boolean; +} + +export function isStringBase64( + v: string, + opts: IsBase64Options = {} +): boolean { + if (opts.allowEmpty === false && v === "") { + return false; + } + + let regex = "(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+\\/]{3}=)?"; + const mimeRegex = "(data:\\w+\\/[a-zA-Z\\+\\-\\.]+;base64,)"; + + if (opts.mimeRequired === true) { + regex = mimeRegex + regex; + } + else if (opts.allowMime === true) { + regex = mimeRegex + "?" + regex; + } + + if (opts.paddingRequired === false) { + regex = "(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}(==)?|[A-Za-z0-9+\\/]{3}=?)?"; + } + + return (new RegExp("^" + regex + "$", "gi")).test(v); +} + export function isSvg( strOrLiteral: ESTreeLiteral | string ): boolean { diff --git a/workspaces/sec-literal/test/isStringBase64.spec.ts b/workspaces/sec-literal/test/isStringBase64.spec.ts new file mode 100644 index 0000000..cd0122d --- /dev/null +++ b/workspaces/sec-literal/test/isStringBase64.spec.ts @@ -0,0 +1,50 @@ +/* eslint-disable @stylistic/max-len */ +// Import Node.js Dependencies +import { test } from "node:test"; +import assert from "node:assert/strict"; + +// Import Internal Dependencies +import { isStringBase64 } from "../src/utils.js"; + +test("isBase64", function isBase64() { + const pngString = "iVBORw0KGgoAAAANSUhEUgAABQAAAALQAQMAAAD1s08VAAAAA1BMVEX/AAAZ4gk3AAAAh0lEQVR42u3BMQEAAADCoPVPbQlPoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4GsTfAAGc95RKAAAAAElFTkSuQmCC"; + const pngStringWithMime = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABQAAAALQAQMAAAD1s08VAAAAA1BMVEX/AAAZ4gk3AAAAh0lEQVR42u3BMQEAAADCoPVPbQlPoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4GsTfAAGc95RKAAAAAElFTkSuQmCC"; + const jpgString = "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEACAhITMkM1EwMFFCLy8vQiccHBwcJyIXFxcXFyIRDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBIjMzNCY0IhgYIhQODg4UFA4ODg4UEQwMDAwMEREMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAAYABgMBIgACEQEDEQH/xABVAAEBAAAAAAAAAAAAAAAAAAAAAxAAAQQCAwEAAAAAAAAAAAAAAgABAxQEIxIkMxMBAQAAAAAAAAAAAAAAAAAAAAARAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AIE7MwkbOUJDJWx+ZjXATitx2/h2bEWvX5Y0npQ7aIiD/9k="; + const jpgStringWithMime = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEACAhITMkM1EwMFFCLy8vQiccHBwcJyIXFxcXFyIRDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBIjMzNCY0IhgYIhQODg4UFA4ODg4UEQwMDAwMEREMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAAYABgMBIgACEQEDEQH/xABVAAEBAAAAAAAAAAAAAAAAAAAAAxAAAQQCAwEAAAAAAAAAAAAAAgABAxQEIxIkMxMBAQAAAAAAAAAAAAAAAAAAAAARAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AIE7MwkbOUJDJWx+ZjXATitx2/h2bEWvX5Y0npQ7aIiD/9k="; + + // Test agains real images + assert.equal(isStringBase64(pngString), true); + assert.equal(isStringBase64(pngStringWithMime), false); + assert.equal(isStringBase64(pngStringWithMime, { allowMime: true }), true); + assert.equal(isStringBase64(pngString, { mimeRequired: true }), false); + assert.equal(isStringBase64(pngStringWithMime, { mimeRequired: true }), true); + assert.equal(isStringBase64(jpgString), true); + assert.equal(isStringBase64(jpgStringWithMime), false); + assert.equal(isStringBase64(jpgStringWithMime, { allowMime: true }), true); + + // helper for creating fake valid mime strings + function createMimeString(mime: string): string { + return `data:${mime};base64,${pngString}`; + } + + // Random complex mime types taken from: + // http://www.freeformatter.com/mime-types-list.html + assert.equal(isStringBase64(createMimeString("application/vnd.apple.installer+xml"), { allowMime: true }), true); + assert.equal(isStringBase64(createMimeString("image/svg+xml"), { allowMime: true }), true); + assert.equal(isStringBase64(createMimeString("application/set-payment-initiation"), { allowMime: true }), true); + assert.equal(isStringBase64(createMimeString("image/vnd.adobe.photoshop"), { allowMime: true }), true); + + assert.equal(isStringBase64("1342234"), false); + assert.equal(isStringBase64("afQ$%rfew"), false); + assert.equal(isStringBase64("dfasdfr342"), false); + assert.equal(isStringBase64("uuLMhh"), false); + assert.equal(isStringBase64("uuLMhh", { paddingRequired: false }), true); + assert.equal(isStringBase64("uuLMhh", { paddingRequired: true }), false); + assert.equal(isStringBase64("uuLMhh=="), true); + assert.equal(isStringBase64("uuLMhh==", { paddingRequired: false }), true); + assert.equal(isStringBase64("uuLMhh==", { paddingRequired: true }), true); + assert.equal(isStringBase64("data:image/png;base64,uuLMhh==", { paddingRequired: true }), false); + assert.equal(isStringBase64("data:image/png;base64,uuLMhh==", { paddingRequired: true, allowMime: true }), true); + assert.equal(isStringBase64(""), true); + assert.equal(isStringBase64("", { allowEmpty: false }), false); +});