mirror of
https://github.com/kou029w/websri.git
synced 2025-04-02 11:46:26 +00:00
Compare commits
67 commits
Author | SHA1 | Date | |
---|---|---|---|
|
345017c52d | ||
|
529d6a3483 | ||
|
09960e348d | ||
|
2e9d3bc281 | ||
|
7e43c94bfb | ||
9a2e7ad45c | |||
d0415bbd04 | |||
|
22450619c4 | ||
83860ef7f8 | |||
93af3bce3c | |||
3d1a4acd2f | |||
52ff1f7e9a | |||
|
c8e7319396 | ||
|
5310a04b87 | ||
|
7d53588259 | ||
|
33044b912a | ||
f4c800793b | |||
|
f2854638df | ||
|
17fce57d8b | ||
|
5f6ceea225 | ||
|
5e369ade90 | ||
8f7b6bd105 | |||
|
472df9a60f | ||
39a1e06c8d | |||
419d7668a9 | |||
02c253b45f | |||
c652fb46f4 | |||
d7cf1bf4dc | |||
d740f57970 | |||
90f6a0844a | |||
ab57344af3 | |||
c085fcbe8f | |||
f7ac85b913 | |||
f538f67971 | |||
|
2f3d3318e2 | ||
|
d9731fb6d7 | ||
|
b3a0c978eb | ||
|
d492ebd876 | ||
1924621a2b | |||
47228186a0 | |||
f8aff38a6b | |||
5ad019029b | |||
ddcb93da0c | |||
9b64464d81 | |||
d7ab327e56 | |||
3bd7f5566e | |||
b6cb686c37 | |||
b8f8c3d209 | |||
beff4e49d5 | |||
9952afecc2 | |||
bd29bf4dc1 | |||
e5f17c3631 | |||
5c5d171791 | |||
9561fb23dc | |||
7b57a930e1 | |||
716d3e88d5 | |||
963d4e2960 | |||
|
f90d76097f | ||
|
445bde4987 | ||
|
af9243c726 | ||
6ec0f55736 | |||
|
79ff27dbe4 | ||
8d6c191978 | |||
628cc0bdee | |||
|
be28d23470 | ||
a154ad7f6f | |||
bfee60800d |
34 changed files with 2682 additions and 1810 deletions
.github/workflows
CHANGELOG.mdREADME.mdpackage-lock.jsonpackage.jsonrenovate.jsonruntime
bun
deno
src
test
create-integrity-metadata-set.tscreate-integrity-metadata.tsget-prioritized-hash-algorithm.tsindex.js
tsup.config.tsintegrity-metadata-set
constructor.tsiterator.tsjoin.tsmatch.tssize.tsstrongest-hash-algorithms.tsstrongest.tsto-json.tsto-string.ts
integrity-metadata
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
|
@ -16,10 +16,10 @@ jobs:
|
|||
contents: write
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: npm
|
||||
|
|
28
.github/workflows/test.yml
vendored
28
.github/workflows/test.yml
vendored
|
@ -1,7 +1,7 @@
|
|||
name: test
|
||||
on: push
|
||||
jobs:
|
||||
nodejs:
|
||||
node:
|
||||
name: Node.js v${{ matrix.node-version }}
|
||||
strategy:
|
||||
matrix:
|
||||
|
@ -11,10 +11,32 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
deno:
|
||||
name: Deno
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- uses: denoland/setup-deno@v1
|
||||
with:
|
||||
deno-version: latest
|
||||
- run: deno task test
|
||||
working-directory: runtime/deno
|
||||
bun:
|
||||
name: Bun
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2
|
||||
with:
|
||||
bun-version: latest
|
||||
- run: bun test
|
||||
working-directory: runtime/bun
|
||||
|
|
35
CHANGELOG.md
35
CHANGELOG.md
|
@ -7,14 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.0.1] - 2025-02-19
|
||||
|
||||
- Fixed "Maximum call stack size exceeded" error when using `IntegrityMetadataSet#strongest`
|
||||
|
||||
## [1.0.0] - 2025-01-06
|
||||
|
||||
- Change `strongest` property type from `Array<IntegrityMetadata>` to `IntegrityMetadataSet`
|
||||
- Improve documentation
|
||||
|
||||
## [0.1.0] - 2024-10-02
|
||||
|
||||
- **IntegrityMetadataSet Enhancements**: Refactored `IntegrityMetadataSet` to improve structure and flexibility, including new methods (`match`, `strongestHashAlgorithms`, `iterator`, and `size`), support for more flexible input types, and enhanced validation logic.
|
||||
- **Type Definitions & Code Readability**: Improved type definitions and enhanced overall code readability.
|
||||
- **Build & Tooling**: Replaced `tsup` with `pkgroll` and `tsx` for bundling.
|
||||
|
||||
## [0.0.3] - 2024-09-03
|
||||
|
||||
- Rename `SubresourceIntegrity` to `IntegrityMetadataSet`
|
||||
|
||||
## [0.0.2] - 2024-09-02
|
||||
|
||||
- Add `types` field in package.json
|
||||
|
||||
## [0.0.1] - 2024-09-02
|
||||
|
||||
- First release
|
||||
|
||||
## [0.0.0]
|
||||
|
||||
[Unreleased]: https://github.com/kou029w/websri
|
||||
[0.0.0]: https://github.com/kou029w/websri
|
||||
|
||||
[unreleased]: https://github.com/kou029w/usri/compare/v0.0.1...HEAD
|
||||
[1.0.1]: https://github.com/kou029w/websri/compare/v1.0.0...v1.0.1
|
||||
[1.0.0]: https://github.com/kou029w/websri/compare/v0.1.0...v1.0.0
|
||||
[0.1.0]: https://github.com/kou029w/websri/compare/v0.0.3...v0.1.0
|
||||
[0.0.3]: https://github.com/kou029w/websri/compare/v0.0.2...v0.0.3
|
||||
[0.0.2]: https://github.com/kou029w/websri/compare/v0.0.1...v0.0.2
|
||||
[0.0.1]: https://github.com/kou029w/usri/releases/tag/v0.0.1
|
||||
[unreleased]: https://github.com/kou029w/websri/compare/v1.0.1...HEAD
|
||||
|
|
|
@ -16,10 +16,13 @@ npm install websri
|
|||
yarn add websri
|
||||
|
||||
# pnpm
|
||||
pnpm install websri
|
||||
pnpm add websri
|
||||
|
||||
# deno
|
||||
deno add npm:websri
|
||||
|
||||
# bun
|
||||
bun install websri
|
||||
bun add websri
|
||||
```
|
||||
|
||||
[Integrity Metadata](https://www.w3.org/TR/SRI/#integrity-metadata):
|
||||
|
|
2961
package-lock.json
generated
2961
package-lock.json
generated
File diff suppressed because it is too large
Load diff
32
package.json
32
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "websri",
|
||||
"version": "0.0.1",
|
||||
"version": "1.0.1",
|
||||
"description": "A universal Subresource Integrity (SRI) utility for Node.js, browsers, Cloudflare Workers, Deno, Bun, and other web-compatible runtimes.",
|
||||
"license": "MIT",
|
||||
"author": "Kohei Watanabe <nebel@fogtype.com>",
|
||||
|
@ -8,7 +8,22 @@
|
|||
"type": "git",
|
||||
"url": "https://github.com/kou029w/websri.git"
|
||||
},
|
||||
"keywords": [
|
||||
"browser",
|
||||
"bun",
|
||||
"cloudflare",
|
||||
"cloudflare-workers",
|
||||
"deno",
|
||||
"hashing",
|
||||
"integrity",
|
||||
"nodejs",
|
||||
"security",
|
||||
"sri",
|
||||
"subresource-integrity",
|
||||
"typescript"
|
||||
],
|
||||
"type": "module",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
"import": {
|
||||
"types": "./dist/index.d.ts",
|
||||
|
@ -23,16 +38,17 @@
|
|||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"prepublishOnly": "tsup",
|
||||
"test": "tsup && node --test",
|
||||
"build": "pkgroll",
|
||||
"prepublishOnly": "pkgroll",
|
||||
"test": "tsx --test $(find test -type f)",
|
||||
"release": "release-it --"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@release-it/keep-a-changelog": "5.0.0",
|
||||
"@types/node": "22.5.2",
|
||||
"release-it": "17.6.0",
|
||||
"tsup": "8.2.4",
|
||||
"typescript": "5.5.4"
|
||||
"@types/node": "22.13.17",
|
||||
"pkgroll": "2.6.0",
|
||||
"release-it": "17.10.0",
|
||||
"tsx": "4.19.3",
|
||||
"typescript": "5.6.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:best-practices", ":automergeAll"]
|
||||
"extends": ["config:best-practices", ":automergeAll", "schedule:monthly"]
|
||||
}
|
||||
|
|
BIN
runtime/bun/bun.lockb
Executable file
BIN
runtime/bun/bun.lockb
Executable file
Binary file not shown.
12
runtime/bun/integrity-metadata.test.ts
Normal file
12
runtime/bun/integrity-metadata.test.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { expect, test } from "bun:test";
|
||||
import { createIntegrityMetadata } from "../../src/index.ts";
|
||||
|
||||
test("createIntegrityMetadata()", async () => {
|
||||
const res = new Response("Hello, world!");
|
||||
const data = await res.arrayBuffer();
|
||||
const integrityMetadata = await createIntegrityMetadata("sha256", data);
|
||||
|
||||
expect(integrityMetadata.toString()).toBe(
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
);
|
||||
});
|
8
runtime/bun/package.json
Normal file
8
runtime/bun/package.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
}
|
27
runtime/bun/tsconfig.json
Normal file
27
runtime/bun/tsconfig.json
Normal file
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
}
|
||||
}
|
8
runtime/deno/deno.json
Normal file
8
runtime/deno/deno.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"tasks": {
|
||||
"test": "deno test --no-check"
|
||||
},
|
||||
"imports": {
|
||||
"@std/assert": "jsr:@std/assert@^1.0.3"
|
||||
}
|
||||
}
|
26
runtime/deno/deno.lock
generated
Normal file
26
runtime/deno/deno.lock
generated
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"version": "3",
|
||||
"packages": {
|
||||
"specifiers": {
|
||||
"jsr:@std/assert@^1.0.3": "jsr:@std/assert@1.0.3",
|
||||
"jsr:@std/internal@^1.0.2": "jsr:@std/internal@1.0.2"
|
||||
},
|
||||
"jsr": {
|
||||
"@std/assert@1.0.3": {
|
||||
"integrity": "b0d03ce1ced880df67132eea140623010d415848df66f6aa5df76507ca7c26d8",
|
||||
"dependencies": [
|
||||
"jsr:@std/internal@^1.0.2"
|
||||
]
|
||||
},
|
||||
"@std/internal@1.0.2": {
|
||||
"integrity": "f4cabe2021352e8bfc24e6569700df87bf070914fc38d4b23eddd20108ac4495"
|
||||
}
|
||||
}
|
||||
},
|
||||
"remote": {},
|
||||
"workspace": {
|
||||
"dependencies": [
|
||||
"jsr:@std/assert@^1.0.3"
|
||||
]
|
||||
}
|
||||
}
|
13
runtime/deno/integrity-metadata.test.ts
Normal file
13
runtime/deno/integrity-metadata.test.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { assertEquals } from "@std/assert";
|
||||
import { createIntegrityMetadata } from "../../src/index.ts";
|
||||
|
||||
Deno.test("createIntegrityMetadata()", async () => {
|
||||
const res = new Response("Hello, world!");
|
||||
const data = await res.arrayBuffer();
|
||||
const integrityMetadata = await createIntegrityMetadata("sha256", data);
|
||||
|
||||
assertEquals(
|
||||
integrityMetadata.toString(),
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
);
|
||||
});
|
439
src/index.ts
439
src/index.ts
|
@ -1,47 +1,130 @@
|
|||
/** Content Security Policy Level 2, section 4.2 */
|
||||
/**
|
||||
* Represents the available hash algorithms used for Subresource Integrity.
|
||||
* @see {@link https://www.w3.org/TR/CSP2/#hash_algo}
|
||||
*/
|
||||
export type HashAlgorithm = "sha256" | "sha384" | "sha512";
|
||||
|
||||
export const supportedHashAlgorithm: ReadonlyArray<HashAlgorithm> = [
|
||||
"sha512",
|
||||
"sha384",
|
||||
"sha256",
|
||||
];
|
||||
|
||||
export const supportedHashAlgorithmName = {
|
||||
/**
|
||||
* A constant object defining the supported hash algorithms and their corresponding string
|
||||
* representations for cryptographic operations. These algorithms are referenced by name when
|
||||
* working with hashing functions in Web Crypto APIs.
|
||||
*/
|
||||
export const supportedHashAlgorithms = {
|
||||
/** SHA-256 hash algorithm */
|
||||
sha256: "SHA-256",
|
||||
/** SHA-384 hash algorithm */
|
||||
sha384: "SHA-384",
|
||||
/** SHA-512 hash algorithm */
|
||||
sha512: "SHA-512",
|
||||
} satisfies Record<HashAlgorithm, string>;
|
||||
} as const satisfies Record<HashAlgorithm, HashAlgorithmIdentifier>;
|
||||
|
||||
export type PrioritizedHash = "" | HashAlgorithm;
|
||||
/**
|
||||
* A union type representing either an empty string or a valid hash algorithm.
|
||||
* The empty string is used when no hash algorithm is selected or is considered equal.
|
||||
*/
|
||||
export type PrioritizedHashAlgorithm = "" | HashAlgorithm;
|
||||
|
||||
export function getPrioritizedHash(
|
||||
/**
|
||||
* Function to prioritize two hash algorithms, returning the stronger or an empty string if both
|
||||
* are unsupported or equal.
|
||||
* @see {@link https://www.w3.org/TR/SRI/#dfn-getprioritizedhashfunction-a-b}
|
||||
* @param a The first hash algorithm to compare.
|
||||
* @param b The second hash algorithm to compare.
|
||||
* @returns The hash algorithm or an empty string if both algorithms are unsupported or equal.
|
||||
*/
|
||||
export type GetPrioritizedHashAlgorithm = (
|
||||
a: HashAlgorithm,
|
||||
b: HashAlgorithm,
|
||||
): PrioritizedHash {
|
||||
) => PrioritizedHashAlgorithm;
|
||||
|
||||
/**
|
||||
* Function to prioritize two hash algorithms, returning the stronger or an empty string if both
|
||||
* are unsupported or equal.
|
||||
* @see {@link https://www.w3.org/TR/SRI/#dfn-getprioritizedhashfunction-a-b}
|
||||
* @param a The first hash algorithm to compare.
|
||||
* @param b The second hash algorithm to compare.
|
||||
* @returns The hash algorithm or an empty string if both algorithms are unsupported or equal.
|
||||
*/
|
||||
export function getPrioritizedHashAlgorithm(
|
||||
a: HashAlgorithm,
|
||||
b: HashAlgorithm,
|
||||
): PrioritizedHashAlgorithm {
|
||||
if (a === b) return "";
|
||||
if (!supportedHashAlgorithm.includes(a)) return "";
|
||||
if (!supportedHashAlgorithm.includes(b)) return "";
|
||||
|
||||
if (!(a in supportedHashAlgorithms)) {
|
||||
return b in supportedHashAlgorithms ? b : "";
|
||||
}
|
||||
|
||||
if (!(b in supportedHashAlgorithms)) {
|
||||
return a in supportedHashAlgorithms ? a : "";
|
||||
}
|
||||
|
||||
return a < b ? b : a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Regular expression for matching integrity metadata format.
|
||||
*/
|
||||
export const IntegrityMetadataRegex =
|
||||
/^(?<alg>sha256|sha384|sha512)-(?<val>(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?)(?:[?](?<opts>[\x21-\x7e]*))?$/;
|
||||
/^(?<alg>sha256|sha384|sha512)-(?<val>(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?)(?:[?](?<opt>[\x21-\x7e]*))?$/;
|
||||
|
||||
/**
|
||||
* Regular expression for separating integrity metadata.
|
||||
*/
|
||||
export const SeparatorRegex = /[^\x21-\x7e]+/;
|
||||
|
||||
/** Integrity Metadata */
|
||||
export class IntegrityMetadata {
|
||||
alg: PrioritizedHash;
|
||||
/**
|
||||
* Represents the structure of integrity metadata used for validating resources with Subresource
|
||||
* Integrity.
|
||||
*/
|
||||
export type IntegrityMetadataLike = {
|
||||
/** Hash algorithm */
|
||||
alg: PrioritizedHashAlgorithm;
|
||||
/** The base64-encoded hash value of the resource */
|
||||
val: string;
|
||||
/** Optional additional attributes */
|
||||
opt?: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Class representing integrity metadata, consisting of a hash algorithm and hash value.
|
||||
*/
|
||||
export class IntegrityMetadata implements IntegrityMetadataLike {
|
||||
/** Hash algorithm */
|
||||
alg: PrioritizedHashAlgorithm;
|
||||
/** The base64-encoded hash value of the resource */
|
||||
val: string;
|
||||
/** Optional additional attributes */
|
||||
opt: string[];
|
||||
|
||||
constructor(integrity: string) {
|
||||
/**
|
||||
* Creates an instance of `IntegrityMetadata` from a given object or string.
|
||||
* @param integrity The integrity metadata input, which can be a string or object.
|
||||
* @example
|
||||
* ```js
|
||||
* new IntegrityMetadata("sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=")
|
||||
* ```
|
||||
*
|
||||
* or
|
||||
*
|
||||
* ```js
|
||||
* new IntegrityMetadata({
|
||||
* alg: "sha256",
|
||||
* val: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
constructor(integrity: IntegrityMetadataLike | string | null | undefined) {
|
||||
const integrityString =
|
||||
typeof integrity === "object" && integrity !== null
|
||||
? IntegrityMetadata.stringify(integrity)
|
||||
: String(integrity ?? "").trim();
|
||||
|
||||
const {
|
||||
alg = "",
|
||||
val = "",
|
||||
opt,
|
||||
} = IntegrityMetadataRegex.exec(integrity)?.groups ?? {};
|
||||
} = IntegrityMetadataRegex.exec(integrityString)?.groups ?? {};
|
||||
|
||||
Object.assign(this, {
|
||||
alg,
|
||||
|
@ -50,118 +133,310 @@ export class IntegrityMetadata {
|
|||
});
|
||||
}
|
||||
|
||||
match(integrity: { alg: PrioritizedHash; val: string }): boolean {
|
||||
return integrity.alg === this.alg && integrity.val === this.val;
|
||||
/**
|
||||
* Compares the current integrity metadata with another object or string.
|
||||
* @param integrity The integrity metadata to compare with.
|
||||
* @returns `true` if the integrity metadata matches, `false` otherwise.
|
||||
* @example
|
||||
* ```js
|
||||
* integrityMetadata.match("sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=")
|
||||
* ```
|
||||
*
|
||||
* or
|
||||
*
|
||||
* ```js
|
||||
* integrityMetadata.match({
|
||||
* alg: "sha256",
|
||||
* val: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
match(integrity: IntegrityMetadataLike | string | null | undefined): boolean {
|
||||
const { alg, val } = new IntegrityMetadata(integrity);
|
||||
if (!alg) return false;
|
||||
if (!val) return false;
|
||||
if (!(alg in supportedHashAlgorithms)) return false;
|
||||
return alg === this.alg && val === this.val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the integrity metadata into a string representation.
|
||||
* @returns The string representation of the integrity metadata.
|
||||
*/
|
||||
toString(): string {
|
||||
return IntegrityMetadata.stringify(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the integrity metadata into a JSON string.
|
||||
* @returns The JSON string representation of the integrity metadata.
|
||||
*/
|
||||
toJSON(): string {
|
||||
return this.toString();
|
||||
}
|
||||
|
||||
static stringify(integrity: {
|
||||
alg: PrioritizedHash;
|
||||
val: string;
|
||||
opt: string[];
|
||||
}): string {
|
||||
if (!integrity.alg) return "";
|
||||
if (!integrity.val) return "";
|
||||
if (!supportedHashAlgorithm.includes(integrity.alg)) return "";
|
||||
return `${integrity.alg}-${[integrity.val, ...integrity.opt].join("?")}`;
|
||||
/**
|
||||
* Static method to stringify an integrity metadata object.
|
||||
* @param integrity The integrity metadata object to stringify.
|
||||
* @returns The stringified integrity metadata.
|
||||
* @example
|
||||
* ```js
|
||||
* IntegrityMetadata.stringify({
|
||||
* alg: "sha256",
|
||||
* val: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
* }) // "sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM="
|
||||
* ```
|
||||
*/
|
||||
static stringify({ alg, val, opt = [] }: IntegrityMetadataLike): string {
|
||||
if (!alg) return "";
|
||||
if (!val) return "";
|
||||
if (!(alg in supportedHashAlgorithms)) return "";
|
||||
return `${alg}-${[val, ...opt].join("?")}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously creates an `IntegrityMetadata` object from a hash algorithm and data.
|
||||
* @param hashAlgorithm The hash algorithm to use (e.g., `sha256`).
|
||||
* @param data The data to hash (in `ArrayBuffer` format).
|
||||
* @param opt Optional additional attributes.
|
||||
* @returns A promise that resolves to an `IntegrityMetadata` object.
|
||||
* @example
|
||||
* ```js
|
||||
* const res = new Response("Hello, world!");
|
||||
* const data = await res.arrayBuffer();
|
||||
* const integrityMetadata = await createIntegrityMetadata("sha256", data);
|
||||
* ```
|
||||
*/
|
||||
export async function createIntegrityMetadata(
|
||||
hashAlgorithm: HashAlgorithm,
|
||||
data: ArrayBuffer,
|
||||
opt: string[] = [],
|
||||
): Promise<string> {
|
||||
): Promise<IntegrityMetadata> {
|
||||
const alg = hashAlgorithm.toLowerCase() as HashAlgorithm;
|
||||
|
||||
if (!supportedHashAlgorithm.includes(alg)) return "";
|
||||
|
||||
const arrayBuffer = await crypto.subtle.digest(
|
||||
supportedHashAlgorithmName[alg.toLowerCase()],
|
||||
data,
|
||||
);
|
||||
if (!(alg in supportedHashAlgorithms)) {
|
||||
return new IntegrityMetadata("");
|
||||
}
|
||||
|
||||
const hashAlgorithmIdentifier = supportedHashAlgorithms[alg];
|
||||
const arrayBuffer = await crypto.subtle.digest(hashAlgorithmIdentifier, data);
|
||||
const val = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
|
||||
const integrity = IntegrityMetadata.stringify({ alg, val, opt });
|
||||
|
||||
return IntegrityMetadata.stringify({
|
||||
alg,
|
||||
val,
|
||||
opt,
|
||||
});
|
||||
return new IntegrityMetadata(integrity);
|
||||
}
|
||||
|
||||
/** Subresource Integrity */
|
||||
export class SubresourceIntegrity extends Map<
|
||||
HashAlgorithm,
|
||||
IntegrityMetadata
|
||||
> {
|
||||
getPrioritizedHash: (a: HashAlgorithm, b: HashAlgorithm) => PrioritizedHash;
|
||||
/**
|
||||
* Options for configuring an `IntegrityMetadataSet`.
|
||||
*/
|
||||
export type IntegrityMetadataSetOptions = {
|
||||
/** A custom function to determine the prioritized hash algorithm. */
|
||||
getPrioritizedHashAlgorithm?: GetPrioritizedHashAlgorithm;
|
||||
};
|
||||
|
||||
constructor(integrity: string, options = { getPrioritizedHash }) {
|
||||
super();
|
||||
/**
|
||||
* Class representing a set of integrity metadata, used for managing multiple hash algorithms and
|
||||
* their associated metadata.
|
||||
*/
|
||||
export class IntegrityMetadataSet {
|
||||
#set: ReadonlyArray<IntegrityMetadata>;
|
||||
#getPrioritizedHashAlgorithm = getPrioritizedHashAlgorithm;
|
||||
|
||||
const integrityMetadata = integrity.split(SeparatorRegex);
|
||||
/**
|
||||
* Create an instance of `IntegrityMetadataSet` from integrity metadata or an array of integrity
|
||||
* metadata.
|
||||
* @param integrity The integrity metadata or an array of integrity metadata.
|
||||
* @param options Optional configuration options for hash algorithm prioritization.
|
||||
* @example
|
||||
* ```js
|
||||
* new IntegrityMetadataSet([
|
||||
* "sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
* "sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r",
|
||||
* "sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==",
|
||||
* ])
|
||||
* ```
|
||||
*
|
||||
* or
|
||||
*
|
||||
* ```js
|
||||
* new IntegrityMetadataSet(`
|
||||
* sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=
|
||||
* sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r
|
||||
* sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==
|
||||
* `)
|
||||
* ```
|
||||
*/
|
||||
constructor(
|
||||
integrity:
|
||||
| ReadonlyArray<IntegrityMetadataLike | string | null | undefined>
|
||||
| IntegrityMetadataLike
|
||||
| string
|
||||
| null
|
||||
| undefined,
|
||||
{
|
||||
getPrioritizedHashAlgorithm:
|
||||
_getPrioritizedHashAlgorithm = getPrioritizedHashAlgorithm,
|
||||
}: IntegrityMetadataSetOptions = {},
|
||||
) {
|
||||
this.#set = [integrity]
|
||||
.flat()
|
||||
.flatMap(
|
||||
(
|
||||
integrity: IntegrityMetadataLike | string | null | undefined,
|
||||
): ReadonlyArray<IntegrityMetadataLike | string | null | undefined> => {
|
||||
if (typeof integrity === "string") {
|
||||
return integrity.split(SeparatorRegex);
|
||||
}
|
||||
|
||||
for (const integrity of integrityMetadata.filter(Boolean)) {
|
||||
const integrityMetadata = new IntegrityMetadata(integrity);
|
||||
return [integrity];
|
||||
},
|
||||
)
|
||||
.map((integrity) => new IntegrityMetadata(integrity))
|
||||
.filter((integrityMetadata) => integrityMetadata.toString() !== "");
|
||||
|
||||
if (integrityMetadata.alg) {
|
||||
this.set(integrityMetadata.alg, integrityMetadata);
|
||||
this.#getPrioritizedHashAlgorithm = _getPrioritizedHashAlgorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables iteration over the set of integrity metadata.
|
||||
* @returns A generator that yields each IntegrityMetadata object.
|
||||
* @example
|
||||
* ```js
|
||||
* [...integrityMetadataSet]
|
||||
* ```
|
||||
*/
|
||||
*[Symbol.iterator](): Generator<IntegrityMetadata> {
|
||||
for (const integrityMetadata of this.#set) {
|
||||
yield new IntegrityMetadata(integrityMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of integrity metadata entries in the set.
|
||||
*/
|
||||
get size(): number {
|
||||
return this.#set.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* The strongest (most secure) integrity metadata from the set.
|
||||
* @see {@link https://www.w3.org/TR/SRI/#get-the-strongest-metadata-from-set}
|
||||
*/
|
||||
get strongest(): IntegrityMetadataSet {
|
||||
let strongest = new IntegrityMetadataSet([]);
|
||||
|
||||
for (const integrityMetadata of this.#set) {
|
||||
const [{ alg } = new IntegrityMetadata("")] = strongest;
|
||||
|
||||
const prioritizedHashAlgorithm = this.#getPrioritizedHashAlgorithm(
|
||||
alg as HashAlgorithm,
|
||||
integrityMetadata.alg as HashAlgorithm,
|
||||
);
|
||||
|
||||
switch (prioritizedHashAlgorithm) {
|
||||
case "":
|
||||
strongest = new IntegrityMetadataSet([
|
||||
...strongest,
|
||||
integrityMetadata,
|
||||
]);
|
||||
break;
|
||||
case integrityMetadata.alg:
|
||||
strongest = new IntegrityMetadataSet(integrityMetadata);
|
||||
break;
|
||||
case alg:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.getPrioritizedHash = options.getPrioritizedHash;
|
||||
return strongest;
|
||||
}
|
||||
|
||||
get strongest(): IntegrityMetadata {
|
||||
const [hashAlgorithm = supportedHashAlgorithm[0]]: HashAlgorithm[] = [
|
||||
...this.keys(),
|
||||
].sort((a, b) => {
|
||||
switch (this.getPrioritizedHash(a, b)) {
|
||||
default:
|
||||
case "":
|
||||
return 0;
|
||||
case a:
|
||||
return -1;
|
||||
case b:
|
||||
return +1;
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Returns an array of the strongest supported hash algorithms in the set.
|
||||
*/
|
||||
get strongestHashAlgorithms(): ReadonlyArray<HashAlgorithm> {
|
||||
const strongestHashAlgorithms = [...this.strongest]
|
||||
.map(({ alg }) => alg as HashAlgorithm)
|
||||
.filter(Boolean);
|
||||
|
||||
return this.get(hashAlgorithm) ?? new IntegrityMetadata("");
|
||||
return [...new Set(strongestHashAlgorithms)];
|
||||
}
|
||||
|
||||
match(integrity: { alg: PrioritizedHash; val: string }): boolean {
|
||||
return this.strongest.match(integrity);
|
||||
/**
|
||||
* Checks if a given integrity metadata object or string matches any in the set.
|
||||
* @param integrity The integrity metadata to match.
|
||||
* @returns `true` if the integrity metadata matches, `false` otherwise.
|
||||
* @example
|
||||
* ```js
|
||||
* integrityMetadataSet.match("sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=")
|
||||
* ```
|
||||
*
|
||||
* or
|
||||
*
|
||||
* ```js
|
||||
* integrityMetadataSet.match({
|
||||
* alg: "sha256",
|
||||
* val: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
match(integrity: IntegrityMetadataLike | string | null | undefined): boolean {
|
||||
return this.#set.some((integrityMetadata) =>
|
||||
integrityMetadata.match(integrity),
|
||||
);
|
||||
}
|
||||
|
||||
join(separator = " ") {
|
||||
return [...this.values()].map(String).join(separator);
|
||||
/**
|
||||
* Joins the integrity metadata in the set into a single string, separated by the specified
|
||||
* separator.
|
||||
* @param separator The separator to use (default is a space).
|
||||
* @returns The joined string representation of the set.
|
||||
*/
|
||||
join(separator = " "): string {
|
||||
return this.#set.map(String).join(separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the set of integrity metadata to a string representation.
|
||||
* @returns The string representation of the set.
|
||||
*/
|
||||
toString(): string {
|
||||
return this.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the set of integrity metadata to a JSON string.
|
||||
* @returns The JSON string representation of the set.
|
||||
*/
|
||||
toJSON(): string {
|
||||
return this.toString();
|
||||
}
|
||||
}
|
||||
|
||||
export async function createSubresourceIntegrity(
|
||||
hashAlgorithms: HashAlgorithm[],
|
||||
/**
|
||||
* Asynchronously creates an `IntegrityMetadataSet` from a set of hash algorithms and data.
|
||||
* @param hashAlgorithms A single hash algorithm or an array of supported hash algorithms.
|
||||
* @param data The data to hash (in `ArrayBuffer` format).
|
||||
* @param options Optional configuration options for the metadata set.
|
||||
* @returns A promise that resolves to an `IntegrityMetadataSet` object.
|
||||
* @example
|
||||
* ```js
|
||||
* const res = new Response("Hello, world!");
|
||||
* const data = await res.arrayBuffer();
|
||||
* const set = await createIntegrityMetadataSet(["sha256", "sha384", "sha512"], data);
|
||||
* ```
|
||||
*/
|
||||
export async function createIntegrityMetadataSet(
|
||||
hashAlgorithms: ReadonlyArray<HashAlgorithm> | HashAlgorithm,
|
||||
data: ArrayBuffer,
|
||||
): Promise<SubresourceIntegrity> {
|
||||
const integrityMetadata = await Promise.all(
|
||||
hashAlgorithms.map((alg) => createIntegrityMetadata(alg, data)),
|
||||
options: IntegrityMetadataSetOptions = {
|
||||
getPrioritizedHashAlgorithm,
|
||||
},
|
||||
): Promise<IntegrityMetadataSet> {
|
||||
const set = await Promise.all(
|
||||
[hashAlgorithms].flat().map((alg) => createIntegrityMetadata(alg, data)),
|
||||
);
|
||||
|
||||
return new SubresourceIntegrity(integrityMetadata.join(" "));
|
||||
return new IntegrityMetadataSet(set, options);
|
||||
}
|
||||
|
|
35
test/create-integrity-metadata-set.ts
Normal file
35
test/create-integrity-metadata-set.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import {
|
||||
createIntegrityMetadataSet,
|
||||
IntegrityMetadata,
|
||||
IntegrityMetadataSet,
|
||||
} from "../src/index.ts";
|
||||
|
||||
test("instantiate a new IntegrityMetadataSet", async function () {
|
||||
const res = new Response("Hello, world!");
|
||||
const data = await res.arrayBuffer();
|
||||
const set = await createIntegrityMetadataSet("sha256", data);
|
||||
|
||||
assert(set instanceof IntegrityMetadataSet);
|
||||
});
|
||||
|
||||
test("instantiate with the specified hash algorithms and ArrayBuffer", async function () {
|
||||
const res = new Response("Hello, world!");
|
||||
const data = await res.arrayBuffer();
|
||||
const set = await createIntegrityMetadataSet(["sha384", "sha512"], data);
|
||||
|
||||
assert.deepEqual(
|
||||
[...set],
|
||||
[
|
||||
new IntegrityMetadata({
|
||||
alg: "sha384",
|
||||
val: "VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r",
|
||||
}),
|
||||
new IntegrityMetadata({
|
||||
alg: "sha512",
|
||||
val: "wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==",
|
||||
}),
|
||||
],
|
||||
);
|
||||
});
|
23
test/create-integrity-metadata.ts
Normal file
23
test/create-integrity-metadata.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import assert from "node:assert";
|
||||
import { test } from "node:test";
|
||||
import { createIntegrityMetadata, IntegrityMetadata } from "../src/index.ts";
|
||||
|
||||
test("instantiate a new IntegrityMetadata", async function () {
|
||||
const res = new Response("Hello, world!");
|
||||
const data = await res.arrayBuffer();
|
||||
const integrityMetadata = await createIntegrityMetadata("sha256", data);
|
||||
|
||||
assert(integrityMetadata instanceof IntegrityMetadata);
|
||||
});
|
||||
|
||||
test("instantiate with the specified hash algorithm and ArrayBuffer", async function () {
|
||||
const res = new Response("Hello, world!");
|
||||
const data = await res.arrayBuffer();
|
||||
const integrityMetadata = await createIntegrityMetadata("sha256", data);
|
||||
|
||||
assert.deepEqual(integrityMetadata, {
|
||||
alg: "sha256",
|
||||
val: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
opt: [],
|
||||
});
|
||||
});
|
31
test/get-prioritized-hash-algorithm.ts
Normal file
31
test/get-prioritized-hash-algorithm.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { getPrioritizedHashAlgorithm } from "../src/index.ts";
|
||||
|
||||
test("return the most collision-resistant hash algorithm", function () {
|
||||
assert.strictEqual(getPrioritizedHashAlgorithm("sha256", "sha512"), "sha512");
|
||||
});
|
||||
|
||||
test("if the priority is equal, return the empty string", function () {
|
||||
assert.strictEqual(getPrioritizedHashAlgorithm("sha256", "sha256"), "");
|
||||
});
|
||||
|
||||
test("if both hash algorithms are not supported, return the empty string", function () {
|
||||
// @ts-expect-error unsupported hash algorithms
|
||||
assert.strictEqual(getPrioritizedHashAlgorithm("md5", "sha1"), "");
|
||||
});
|
||||
|
||||
test("if one of the hash algorithms is unsupported, return the supported hash algorithm", function () {
|
||||
// @ts-expect-error unsupported hash algorithms
|
||||
assert.strictEqual(getPrioritizedHashAlgorithm("md5", "sha256"), "sha256");
|
||||
});
|
||||
|
||||
test("if both strings are empty, return the empty string", function () {
|
||||
// @ts-expect-error empty string
|
||||
assert.strictEqual(getPrioritizedHashAlgorithm("", ""), "");
|
||||
});
|
||||
|
||||
test("if either is the empty string, it return the supported hash algorithm", function () {
|
||||
// @ts-expect-error empty string
|
||||
assert.strictEqual(getPrioritizedHashAlgorithm("sha256", ""), "sha256");
|
||||
});
|
|
@ -1,39 +0,0 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import {
|
||||
createIntegrityMetadata,
|
||||
createSubresourceIntegrity,
|
||||
SubresourceIntegrity,
|
||||
} from "../dist/index.js";
|
||||
|
||||
test("createIntegrityMetadata()", async function () {
|
||||
const res = new Response("Hello, world!");
|
||||
const data = await res.arrayBuffer();
|
||||
const integrityMetadata = await createIntegrityMetadata("sha256", data);
|
||||
|
||||
assert.strictEqual(
|
||||
integrityMetadata.toString(),
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
);
|
||||
});
|
||||
|
||||
test("createSubresourceIntegrity()", async function () {
|
||||
const res = new Response("Hello, world!");
|
||||
const data = await res.arrayBuffer();
|
||||
const sri = await createSubresourceIntegrity(["sha384", "sha512"], data);
|
||||
|
||||
assert.strictEqual(
|
||||
sri.toString(),
|
||||
"sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==",
|
||||
);
|
||||
});
|
||||
|
||||
test("SubresourceIntegrity.strongest", async function () {
|
||||
const { strongest } = new SubresourceIntegrity(`
|
||||
sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=
|
||||
sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r
|
||||
sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==
|
||||
`);
|
||||
|
||||
assert.strictEqual(strongest.alg, "sha512");
|
||||
});
|
196
test/integrity-metadata-set/constructor.ts
Normal file
196
test/integrity-metadata-set/constructor.ts
Normal file
|
@ -0,0 +1,196 @@
|
|||
import assert from "node:assert";
|
||||
import { test } from "node:test";
|
||||
import { IntegrityMetadataSet } from "../../src/index.ts";
|
||||
|
||||
test("supports SHA-256", function () {
|
||||
const set = new IntegrityMetadataSet(
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
[...set],
|
||||
[
|
||||
{
|
||||
alg: "sha256",
|
||||
val: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
opt: [],
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("supports SHA-384", function () {
|
||||
const set = new IntegrityMetadataSet(
|
||||
"sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r",
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
[...set],
|
||||
[
|
||||
{
|
||||
alg: "sha384",
|
||||
val: "VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r",
|
||||
opt: [],
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("supports SHA-512", function () {
|
||||
const set = new IntegrityMetadataSet(
|
||||
"sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==",
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
[...set],
|
||||
[
|
||||
{
|
||||
alg: "sha512",
|
||||
val: "wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==",
|
||||
opt: [],
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("accepts options", function () {
|
||||
const set = new IntegrityMetadataSet(
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=?foo?bar",
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
[...set],
|
||||
[
|
||||
{
|
||||
alg: "sha256",
|
||||
val: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
opt: ["foo", "bar"],
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("accepts an IntegrityMetadata like object as input", function () {
|
||||
const set = new IntegrityMetadataSet({
|
||||
alg: "sha256",
|
||||
val: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
[...set],
|
||||
[
|
||||
{
|
||||
alg: "sha256",
|
||||
val: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
opt: [],
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("multiple algorithms can be accepted", function () {
|
||||
const set = new IntegrityMetadataSet([
|
||||
{
|
||||
alg: "sha256",
|
||||
val: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
},
|
||||
`
|
||||
sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r
|
||||
sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==
|
||||
`,
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
[...set],
|
||||
[
|
||||
...new IntegrityMetadataSet([
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
"sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r",
|
||||
"sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==",
|
||||
]),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("multiple overlapping algorithms can be accepted", function () {
|
||||
const set = new IntegrityMetadataSet([
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
"sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=",
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
[...set],
|
||||
[
|
||||
...new IntegrityMetadataSet([
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
"sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=",
|
||||
]),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("trims leading and trailing whitespace", function () {
|
||||
const set = new IntegrityMetadataSet(
|
||||
"\t sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=\u0020\u00a0\u1680\u2000\u2001\u2002\u3000",
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
[...set],
|
||||
[
|
||||
{
|
||||
alg: "sha256",
|
||||
val: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
opt: [],
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("whitespace can be analyzed as entry separator", function () {
|
||||
const set = new IntegrityMetadataSet(
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=\t\u0020\u00a0\u1680sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r\u2000\u2001\u2002\u3000sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==",
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
[...set],
|
||||
[
|
||||
...new IntegrityMetadataSet([
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
"sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r",
|
||||
"sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==",
|
||||
]),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("discards unsupported hash algorithm", function () {
|
||||
const set = new IntegrityMetadataSet("sha1-lDpwLQbzRZmu4fjajvn3KWAx1pk=");
|
||||
|
||||
assert.deepEqual([...set], []);
|
||||
});
|
||||
|
||||
test("discards null input", function () {
|
||||
const set = new IntegrityMetadataSet(null);
|
||||
|
||||
assert.deepEqual([...set], []);
|
||||
});
|
||||
|
||||
test("discards empty string input", function () {
|
||||
const set = new IntegrityMetadataSet([]);
|
||||
|
||||
assert.deepEqual([...set], []);
|
||||
});
|
||||
|
||||
test("discards invalid value", function () {
|
||||
const set = new IntegrityMetadataSet("md5\0/..invalid-value");
|
||||
|
||||
assert.deepEqual([...set], []);
|
||||
});
|
||||
|
||||
test("discards invalid values in a list of multiple inputs", function () {
|
||||
const set = new IntegrityMetadataSet(
|
||||
"sha1-lDpwLQbzRZmu4fjajvn3KWAx1pk= md5\0/..invalid-value",
|
||||
);
|
||||
|
||||
assert.deepEqual([...set], []);
|
||||
});
|
32
test/integrity-metadata-set/iterator.ts
Normal file
32
test/integrity-metadata-set/iterator.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import assert from "node:assert";
|
||||
import { test } from "node:test";
|
||||
import { IntegrityMetadata, IntegrityMetadataSet } from "../../src/index.ts";
|
||||
|
||||
test("correctly iterate over the set", function () {
|
||||
const integrityMetadataSet = new IntegrityMetadataSet(`
|
||||
sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=
|
||||
sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r
|
||||
sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==
|
||||
`);
|
||||
|
||||
assert.deepEqual(
|
||||
[...integrityMetadataSet],
|
||||
[
|
||||
new IntegrityMetadata(
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
),
|
||||
new IntegrityMetadata(
|
||||
"sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r",
|
||||
),
|
||||
new IntegrityMetadata(
|
||||
"sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==",
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("if the empty set, return the empty set", function () {
|
||||
const integrityMetadataSet = new IntegrityMetadataSet([]);
|
||||
|
||||
assert.deepEqual([...integrityMetadataSet], []);
|
||||
});
|
32
test/integrity-metadata-set/join.ts
Normal file
32
test/integrity-metadata-set/join.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { IntegrityMetadataSet } from "../../src/index.ts";
|
||||
|
||||
test("join() can be used", function () {
|
||||
const integrityMetadataSet = new IntegrityMetadataSet(
|
||||
`
|
||||
sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r
|
||||
sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==
|
||||
`,
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
integrityMetadataSet.join(),
|
||||
"sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==",
|
||||
);
|
||||
});
|
||||
|
||||
test("a separator can be specified", function () {
|
||||
const integrityMetadataSet = new IntegrityMetadataSet(
|
||||
`
|
||||
sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r
|
||||
sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==
|
||||
`,
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
integrityMetadataSet.join("\n"),
|
||||
`sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r
|
||||
sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==`,
|
||||
);
|
||||
});
|
102
test/integrity-metadata-set/match.ts
Normal file
102
test/integrity-metadata-set/match.ts
Normal file
|
@ -0,0 +1,102 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { IntegrityMetadataSet } from "../../src/index.ts";
|
||||
|
||||
test("if the hash values match, return true", function () {
|
||||
const integrityMetadata = new IntegrityMetadataSet(
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
integrityMetadata.match({
|
||||
alg: "sha256",
|
||||
val: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
}),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("if the hash of one of the selected algorithms matches, return true", function () {
|
||||
const integrityMetadata = new IntegrityMetadataSet([
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
"sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=",
|
||||
]);
|
||||
|
||||
assert.strictEqual(
|
||||
integrityMetadata.match({
|
||||
alg: "sha256",
|
||||
val: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
}),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("a string can be specified", function () {
|
||||
const integrityMetadata = new IntegrityMetadataSet(
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
integrityMetadata.match(
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("if the hash algorithms are different, return false", function () {
|
||||
const integrityMetadata = new IntegrityMetadataSet(
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
integrityMetadata.match({
|
||||
alg: "sha512",
|
||||
val: "MJ7MSJwS1utMxA9QyQLytNDtd+5RGnx6m808qG1M2G+YndNbxf9JlnDaNCVbRbDP2DDoH2Bdz33FVC6TrpzXbw==",
|
||||
}),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("if the hash values are different, return false", function () {
|
||||
const integrityMetadata = new IntegrityMetadataSet(
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
integrityMetadata.match({
|
||||
alg: "sha256",
|
||||
val: "uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=",
|
||||
}),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("if the hash algorithm is unsupported, return false", function () {
|
||||
const integrityMetadata = new IntegrityMetadataSet(
|
||||
"sha1-lDpwLQbzRZmu4fjajvn3KWAx1pk=",
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
integrityMetadata.match("sha1-lDpwLQbzRZmu4fjajvn3KWAx1pk="),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("if null, return false", function () {
|
||||
const integrityMetadata = new IntegrityMetadataSet(null);
|
||||
|
||||
assert.strictEqual(integrityMetadata.match(null), false);
|
||||
});
|
||||
|
||||
test("if empty, return false", function () {
|
||||
const integrityMetadata = new IntegrityMetadataSet([]);
|
||||
|
||||
assert.strictEqual(integrityMetadata.match(""), false);
|
||||
});
|
||||
|
||||
test("if invalid value, return false", function () {
|
||||
const integrityMetadata = new IntegrityMetadataSet("md5\0/..invalid-value");
|
||||
|
||||
assert.strictEqual(integrityMetadata.match("md5\0/..invalid-value"), false);
|
||||
});
|
19
test/integrity-metadata-set/size.ts
Normal file
19
test/integrity-metadata-set/size.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { IntegrityMetadataSet } from "../../src/index.ts";
|
||||
|
||||
test("return the correct size of the set", function () {
|
||||
const integrityMetadataSet = new IntegrityMetadataSet(`
|
||||
sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=
|
||||
sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r
|
||||
sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==
|
||||
`);
|
||||
|
||||
assert.strictEqual(integrityMetadataSet.size, 3);
|
||||
});
|
||||
|
||||
test("if the empty set, return 0", function () {
|
||||
const integrityMetadataSet = new IntegrityMetadataSet([]);
|
||||
|
||||
assert.strictEqual(integrityMetadataSet.size, 0);
|
||||
});
|
39
test/integrity-metadata-set/strongest-hash-algorithms.ts
Normal file
39
test/integrity-metadata-set/strongest-hash-algorithms.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import assert from "node:assert";
|
||||
import { test } from "node:test";
|
||||
import { IntegrityMetadataSet } from "../../src/index.ts";
|
||||
|
||||
test("pick the strongest hash algorithms", function () {
|
||||
const integrityMetadataSet = new IntegrityMetadataSet(`
|
||||
sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=
|
||||
sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r
|
||||
sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==
|
||||
`);
|
||||
|
||||
assert.deepEqual(integrityMetadataSet.strongestHashAlgorithms, ["sha512"]);
|
||||
});
|
||||
|
||||
test("if there are no supported algorithms, return the empty set", function () {
|
||||
const integrityMetadataSet = new IntegrityMetadataSet(`
|
||||
sha1-lDpwLQbzRZmu4fjajvn3KWAx1pk=
|
||||
md5-bNNVbesNpUvKBgtMOUeYOQ==
|
||||
`);
|
||||
|
||||
assert.deepEqual(integrityMetadataSet.strongestHashAlgorithms, []);
|
||||
});
|
||||
|
||||
test("custom getPrioritizedHashAlgorithm function can be used", function () {
|
||||
const integrityMetadataSet = new IntegrityMetadataSet(
|
||||
`
|
||||
sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=
|
||||
sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r
|
||||
sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==
|
||||
`,
|
||||
{
|
||||
getPrioritizedHashAlgorithm() {
|
||||
return "sha384";
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
assert.deepEqual(integrityMetadataSet.strongestHashAlgorithms, ["sha384"]);
|
||||
});
|
63
test/integrity-metadata-set/strongest.ts
Normal file
63
test/integrity-metadata-set/strongest.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import assert from "node:assert";
|
||||
import { test } from "node:test";
|
||||
import { IntegrityMetadataSet } from "../../src/index.ts";
|
||||
|
||||
test("pick the strongest metadata from set", function () {
|
||||
const integrityMetadataSet = new IntegrityMetadataSet(`
|
||||
sha256-gxZXfeA3KCK+ZyBybEt6liVPg+FWGf/KLVU6rufBujE=
|
||||
sha384-LDW1hUX1OX+VZsNmW+LELiky69a4xF+FfVsTlqZOhqPiPj5YYo20jP6C8H8uXMZf
|
||||
sha512-aqnrVqlE3w/CWs51jb3FHCsFSBwfpecdXHaFFYZNkxfW2Z1qyJm4mA9iCPK11KeWwEa8rbMDq7l6IrnevQuOQw==
|
||||
sha512-P8q/bH6NoZs5MnZKL9D/r/oEZOlyEAmSfuXuJchD2WeXnKbnfcO3fF0WvO6CiqZUGWsEREs9BWLrv1xr3NPOLg==
|
||||
`);
|
||||
|
||||
assert.deepEqual(
|
||||
[...integrityMetadataSet.strongest],
|
||||
[
|
||||
{
|
||||
alg: "sha512",
|
||||
val: "aqnrVqlE3w/CWs51jb3FHCsFSBwfpecdXHaFFYZNkxfW2Z1qyJm4mA9iCPK11KeWwEa8rbMDq7l6IrnevQuOQw==",
|
||||
opt: [],
|
||||
},
|
||||
{
|
||||
alg: "sha512",
|
||||
val: "P8q/bH6NoZs5MnZKL9D/r/oEZOlyEAmSfuXuJchD2WeXnKbnfcO3fF0WvO6CiqZUGWsEREs9BWLrv1xr3NPOLg==",
|
||||
opt: [],
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("if there are no supported algorithms, return the empty set", function () {
|
||||
const integrityMetadataSet = new IntegrityMetadataSet(`
|
||||
sha1-lDpwLQbzRZmu4fjajvn3KWAx1pk=
|
||||
md5-bNNVbesNpUvKBgtMOUeYOQ==
|
||||
`);
|
||||
|
||||
assert.deepEqual([...integrityMetadataSet.strongest], []);
|
||||
});
|
||||
|
||||
test("custom getPrioritizedHashAlgorithm function can be used", function () {
|
||||
const integrityMetadataSet = new IntegrityMetadataSet(
|
||||
`
|
||||
sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=
|
||||
sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r
|
||||
sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==
|
||||
`,
|
||||
{
|
||||
getPrioritizedHashAlgorithm() {
|
||||
return "sha384";
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
[...integrityMetadataSet.strongest],
|
||||
[
|
||||
{
|
||||
alg: "sha384",
|
||||
val: "VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r",
|
||||
opt: [],
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
17
test/integrity-metadata-set/to-json.ts
Normal file
17
test/integrity-metadata-set/to-json.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { IntegrityMetadataSet } from "../../src/index.ts";
|
||||
|
||||
test("toJSON() can be used", function () {
|
||||
const integrityMetadataSet = new IntegrityMetadataSet(
|
||||
`
|
||||
sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r
|
||||
sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==
|
||||
`,
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
integrityMetadataSet.toJSON(),
|
||||
"sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==",
|
||||
);
|
||||
});
|
17
test/integrity-metadata-set/to-string.ts
Normal file
17
test/integrity-metadata-set/to-string.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { IntegrityMetadataSet } from "../../src/index.ts";
|
||||
|
||||
test("toString() can be used", function () {
|
||||
const integrityMetadataSet = new IntegrityMetadataSet(
|
||||
`
|
||||
sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r
|
||||
sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==
|
||||
`,
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
integrityMetadataSet.toString(),
|
||||
"sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==",
|
||||
);
|
||||
});
|
112
test/integrity-metadata/constructor.ts
Normal file
112
test/integrity-metadata/constructor.ts
Normal file
|
@ -0,0 +1,112 @@
|
|||
import assert from "node:assert";
|
||||
import { test } from "node:test";
|
||||
import { IntegrityMetadata } from "../../src/index.ts";
|
||||
|
||||
test("supports SHA-256", function () {
|
||||
const integrityMetadata = new IntegrityMetadata(
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
);
|
||||
|
||||
assert.deepEqual(integrityMetadata, {
|
||||
alg: "sha256",
|
||||
val: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
opt: [],
|
||||
});
|
||||
});
|
||||
|
||||
test("supports SHA-384", function () {
|
||||
const integrityMetadata = new IntegrityMetadata(
|
||||
"sha384-VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r",
|
||||
);
|
||||
|
||||
assert.deepEqual(integrityMetadata, {
|
||||
alg: "sha384",
|
||||
val: "VbxVaw0v4Pzlgrpf4Huq//A1ZTY4x6wNVJTCpkwL6hzFczHHwSpFzbyn9MNKCJ7r",
|
||||
opt: [],
|
||||
});
|
||||
});
|
||||
|
||||
test("supports SHA-512", function () {
|
||||
const integrityMetadata = new IntegrityMetadata(
|
||||
"sha512-wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==",
|
||||
);
|
||||
|
||||
assert.deepEqual(integrityMetadata, {
|
||||
alg: "sha512",
|
||||
val: "wVJ82JPBJHc9gRkRlwyP5uhX1t9dySJr2KFgYUwM2WOk3eorlLt9NgIe+dhl1c6ilKgt1JoLsmn1H256V/eUIQ==",
|
||||
opt: [],
|
||||
});
|
||||
});
|
||||
|
||||
test("accepts options", function () {
|
||||
const integrityMetadata = new IntegrityMetadata(
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=?foo?bar",
|
||||
);
|
||||
|
||||
assert.deepEqual(integrityMetadata, {
|
||||
alg: "sha256",
|
||||
val: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
opt: ["foo", "bar"],
|
||||
});
|
||||
});
|
||||
|
||||
test("accepts an IntegrityMetadata like object as input", function () {
|
||||
const integrityMetadata = new IntegrityMetadata({
|
||||
alg: "sha256",
|
||||
val: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
integrityMetadata,
|
||||
new IntegrityMetadata(
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test("trims leading and trailing whitespace", function () {
|
||||
const integrityMetadata = new IntegrityMetadata(
|
||||
"\t sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=\u0020\u00a0\u1680\u2000\u2001\u2002\u3000",
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
integrityMetadata,
|
||||
new IntegrityMetadata(
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test("discards unsupported hash algorithm", function () {
|
||||
const integrityMetadata = new IntegrityMetadata(
|
||||
"sha1-lDpwLQbzRZmu4fjajvn3KWAx1pk=",
|
||||
);
|
||||
|
||||
assert.deepEqual(integrityMetadata, new IntegrityMetadata(""));
|
||||
});
|
||||
|
||||
test("discards null input", function () {
|
||||
const integrityMetadata = new IntegrityMetadata(null);
|
||||
|
||||
assert.deepEqual(integrityMetadata, new IntegrityMetadata(""));
|
||||
});
|
||||
|
||||
test("discards empty string input", function () {
|
||||
const integrityMetadata = new IntegrityMetadata("");
|
||||
|
||||
assert.deepEqual(integrityMetadata, new IntegrityMetadata(""));
|
||||
});
|
||||
|
||||
test("discards invalid value", function () {
|
||||
const integrityMetadata = new IntegrityMetadata("md5\0/..invalid-value");
|
||||
|
||||
assert.deepEqual(integrityMetadata, new IntegrityMetadata(""));
|
||||
});
|
||||
|
||||
test("discards invalid values in a list of multiple inputs", function () {
|
||||
const integrityMetadata = new IntegrityMetadata(
|
||||
"sha1-lDpwLQbzRZmu4fjajvn3KWAx1pk= md5\0/..invalid-value",
|
||||
);
|
||||
|
||||
assert.deepEqual(integrityMetadata, new IntegrityMetadata(""));
|
||||
});
|
82
test/integrity-metadata/match.ts
Normal file
82
test/integrity-metadata/match.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { IntegrityMetadata } from "../../src/index.ts";
|
||||
|
||||
test("if the hash values match, return true", function () {
|
||||
const integrityMetadata = new IntegrityMetadata(
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
integrityMetadata.match({
|
||||
alg: "sha256",
|
||||
val: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
}),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("if the hash algorithms are different, return false", function () {
|
||||
const integrityMetadata = new IntegrityMetadata(
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
integrityMetadata.match({
|
||||
alg: "sha512",
|
||||
val: "MJ7MSJwS1utMxA9QyQLytNDtd+5RGnx6m808qG1M2G+YndNbxf9JlnDaNCVbRbDP2DDoH2Bdz33FVC6TrpzXbw==",
|
||||
}),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("if the hash values are different, return false", function () {
|
||||
const integrityMetadata = new IntegrityMetadata(
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
integrityMetadata.match({
|
||||
alg: "sha256",
|
||||
val: "uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=",
|
||||
}),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("if the hash algorithm is unsupported, return false", function () {
|
||||
const integrityMetadata = new IntegrityMetadata(
|
||||
"sha1-lDpwLQbzRZmu4fjajvn3KWAx1pk=",
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
integrityMetadata.match(
|
||||
new IntegrityMetadata("sha1-lDpwLQbzRZmu4fjajvn3KWAx1pk="),
|
||||
),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("if null, return false", function () {
|
||||
const integrityMetadata = new IntegrityMetadata(null);
|
||||
|
||||
assert.strictEqual(
|
||||
integrityMetadata.match(new IntegrityMetadata(null)),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("if empty, return false", function () {
|
||||
const integrityMetadata = new IntegrityMetadata("");
|
||||
|
||||
assert.strictEqual(integrityMetadata.match(new IntegrityMetadata("")), false);
|
||||
});
|
||||
|
||||
test("if invalid value, return false", function () {
|
||||
const integrityMetadata = new IntegrityMetadata("md5\0/..invalid-value");
|
||||
|
||||
assert.strictEqual(
|
||||
integrityMetadata.match(new IntegrityMetadata("md5\0/..invalid-value")),
|
||||
false,
|
||||
);
|
||||
});
|
13
test/integrity-metadata/stringify.ts
Normal file
13
test/integrity-metadata/stringify.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { IntegrityMetadata } from "../../src/index.ts";
|
||||
|
||||
test("IntegrityMetadata like object can be serialized", function () {
|
||||
assert.strictEqual(
|
||||
IntegrityMetadata.stringify({
|
||||
alg: "sha256",
|
||||
val: "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
}),
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
);
|
||||
});
|
14
test/integrity-metadata/to-json.ts
Normal file
14
test/integrity-metadata/to-json.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { IntegrityMetadata } from "../../src/index.ts";
|
||||
|
||||
test("toJSON() can be used", function () {
|
||||
const integrityMetadata = new IntegrityMetadata(
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
integrityMetadata.toJSON(),
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
);
|
||||
});
|
14
test/integrity-metadata/to-string.ts
Normal file
14
test/integrity-metadata/to-string.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { IntegrityMetadata } from "../../src/index.ts";
|
||||
|
||||
test("toString() can be used", function () {
|
||||
const integrityMetadata = new IntegrityMetadata(
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
integrityMetadata.toString(),
|
||||
"sha256-MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=",
|
||||
);
|
||||
});
|
|
@ -1,10 +0,0 @@
|
|||
import { defineConfig } from "tsup";
|
||||
|
||||
export default defineConfig(() => {
|
||||
return {
|
||||
clean: true,
|
||||
dts: true,
|
||||
entry: ["src"],
|
||||
format: ["cjs", "esm"],
|
||||
};
|
||||
});
|
Loading…
Add table
Reference in a new issue