@chr33s/pdf-common 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base64.d.ts +7 -0
- package/dist/base64.js +24 -0
- package/dist/base64.js.map +1 -0
- package/dist/brotli.d.ts +9 -0
- package/dist/brotli.js +2015 -0
- package/dist/brotli.js.map +1 -0
- package/dist/compression.d.ts +3 -0
- package/dist/compression.js +31 -0
- package/dist/compression.js.map +1 -0
- package/dist/crypto/aes.d.ts +17 -0
- package/dist/crypto/aes.js +60 -0
- package/dist/crypto/aes.js.map +1 -0
- package/dist/crypto/index.d.ts +7 -0
- package/dist/crypto/index.js +8 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/crypto/md5.d.ts +3 -0
- package/dist/crypto/md5.js +9 -0
- package/dist/crypto/md5.js.map +1 -0
- package/dist/crypto/mode.d.ts +5 -0
- package/dist/crypto/mode.js +7 -0
- package/dist/crypto/mode.js.map +1 -0
- package/dist/crypto/padding.d.ts +7 -0
- package/dist/crypto/padding.js +27 -0
- package/dist/crypto/padding.js.map +1 -0
- package/dist/crypto/rc4.d.ts +10 -0
- package/dist/crypto/rc4.js +48 -0
- package/dist/crypto/rc4.js.map +1 -0
- package/dist/crypto/sha256.d.ts +3 -0
- package/dist/crypto/sha256.js +9 -0
- package/dist/crypto/sha256.js.map +1 -0
- package/dist/crypto/word-array.d.ts +28 -0
- package/dist/crypto/word-array.js +116 -0
- package/dist/crypto/word-array.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/package.json +34 -0
- package/src/base64.ts +34 -0
- package/src/brotli.ts +2158 -0
- package/src/compression.ts +35 -0
- package/src/crypto/aes.ts +93 -0
- package/src/crypto/index.ts +7 -0
- package/src/crypto/md5.ts +10 -0
- package/src/crypto/mode.ts +11 -0
- package/src/crypto/padding.ts +36 -0
- package/src/crypto/rc4.ts +65 -0
- package/src/crypto/sha256.ts +10 -0
- package/src/crypto/word-array.ts +147 -0
- package/src/index.ts +4 -0
- package/test/base64.test.ts +173 -0
- package/test/brotli.test.data/0000000000000000.empty.br +1 -0
- package/test/brotli.test.data/126d9a84a3d1b3ad.xyzzy.br +2 -0
- package/test/brotli.test.data/1f39ef98802627d5.bref65536.br +0 -0
- package/test/brotli.test.data/24b2c7600b8207e2.random.br +0 -0
- package/test/brotli.test.data/5b5eb8c2e54aa1c4.fox.br +1 -0
- package/test/brotli.test.data/5ea84b0ab2a9d2f3.ascii.br +0 -0
- package/test/brotli.test.data/62652dac985a31f1.monkey.br +0 -0
- package/test/brotli.test.data/68333cc8433f3ab8.16k_minus_one.br +0 -0
- package/test/brotli.test.data/7da01857468c3de8.buffer_sized_chunks.br +0 -0
- package/test/brotli.test.data/89988757bded53ae.x10y10.br +0 -0
- package/test/brotli.test.data/940493692ce62466.E.coli.br +0 -0
- package/test/brotli.test.data/c3f720395238182c.world192.br +0 -0
- package/test/brotli.test.data/c5a4246584d690a7.16k_plus_one.br +0 -0
- package/test/brotli.test.data/c895e0c1b11448fa.ukkonooa.br +0 -0
- package/test/brotli.test.data/d238c71341928567.allbytevalues_twice.br +0 -0
- package/test/brotli.test.data/d38da8261aedc293.bible.br +0 -0
- package/test/brotli.test.data/d6a9c5da7ba30f21.allbytevalues_16k.br +0 -0
- package/test/brotli.test.data/f121ac88218962d7.x.br +0 -0
- package/test/brotli.test.data/f6083a8a3754518c.x64.br +0 -0
- package/test/brotli.test.file +201 -0
- package/test/brotli.test.file.br +0 -0
- package/test/brotli.test.ts +2197 -0
- package/test/compression.test.ts +93 -0
- package/test/crypto.test.ts +157 -0
- package/tsconfig.json +10 -0
- package/tsconfig.typecheck.json +14 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type CompressionFormat = "deflate" | "deflate-raw" | "gzip";
|
|
2
|
+
|
|
3
|
+
function concatUint8Arrays(arrays: Uint8Array[]) {
|
|
4
|
+
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
|
5
|
+
const result = new Uint8Array(totalLength);
|
|
6
|
+
let offset = 0;
|
|
7
|
+
for (const arr of arrays) {
|
|
8
|
+
result.set(arr, offset);
|
|
9
|
+
offset += arr.length;
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function readAllChunks(stream: ReadableStream<Uint8Array>) {
|
|
15
|
+
const reader = stream.getReader();
|
|
16
|
+
const chunks: Uint8Array[] = [];
|
|
17
|
+
|
|
18
|
+
while (true) {
|
|
19
|
+
const { done, value } = await reader.read();
|
|
20
|
+
if (done) break;
|
|
21
|
+
if (value) chunks.push(value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return concatUint8Arrays(chunks);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function deflate(data: Uint8Array, format: CompressionFormat = "deflate") {
|
|
28
|
+
const stream = new Blob([data]).stream().pipeThrough(new CompressionStream(format));
|
|
29
|
+
return readAllChunks(stream);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function inflate(data: Uint8Array, format: CompressionFormat = "deflate") {
|
|
33
|
+
const stream = new Blob([data]).stream().pipeThrough(new DecompressionStream(format));
|
|
34
|
+
return readAllChunks(stream);
|
|
35
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { createCipheriv, createDecipheriv } from "node:crypto";
|
|
3
|
+
import type { Mode } from "./mode.js";
|
|
4
|
+
import { CBC } from "./mode.js";
|
|
5
|
+
import type { Padding } from "./padding.js";
|
|
6
|
+
import { Pkcs7 } from "./padding.js";
|
|
7
|
+
import { WordArray } from "./word-array.js";
|
|
8
|
+
|
|
9
|
+
export interface CipherResult {
|
|
10
|
+
ciphertext: WordArray;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AESOptions {
|
|
14
|
+
mode?: Mode;
|
|
15
|
+
padding?: Padding;
|
|
16
|
+
iv?: WordArray;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface AESStatic {
|
|
20
|
+
encrypt(message: WordArray, key: WordArray, options?: AESOptions): CipherResult;
|
|
21
|
+
decrypt(ciphertext: WordArray, key: WordArray, options?: AESOptions): CipherResult;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getAlgorithm(keyBytes: Uint8Array, mode: Mode): string {
|
|
25
|
+
const keyBits = keyBytes.length * 8;
|
|
26
|
+
const modeName = mode.name.toLowerCase();
|
|
27
|
+
return `aes-${keyBits}-${modeName}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function encrypt(message: WordArray, key: WordArray, options: AESOptions = {}): CipherResult {
|
|
31
|
+
const mode = options.mode || CBC;
|
|
32
|
+
const padding = options.padding || Pkcs7;
|
|
33
|
+
|
|
34
|
+
const keyBytes = key.toUint8Array();
|
|
35
|
+
const algorithm = getAlgorithm(keyBytes, mode);
|
|
36
|
+
|
|
37
|
+
// Clone and pad the message
|
|
38
|
+
const paddedMessage = message.clone();
|
|
39
|
+
padding.pad(paddedMessage, 4); // AES block size is 4 words (16 bytes)
|
|
40
|
+
const messageBytes = paddedMessage.toUint8Array();
|
|
41
|
+
|
|
42
|
+
// Get IV (for CBC mode)
|
|
43
|
+
let iv: Uint8Array | null = null;
|
|
44
|
+
if (mode === CBC) {
|
|
45
|
+
iv = options.iv ? options.iv.toUint8Array() : new Uint8Array(16);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const cipher = createCipheriv(algorithm, keyBytes, iv);
|
|
49
|
+
cipher.setAutoPadding(false); // We handle padding ourselves
|
|
50
|
+
|
|
51
|
+
const encrypted = Buffer.concat([cipher.update(messageBytes), cipher.final()]);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
ciphertext: WordArray.fromUint8Array(new Uint8Array(encrypted)),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* AES decryption using node:crypto
|
|
60
|
+
*/
|
|
61
|
+
function decrypt(ciphertext: WordArray, key: WordArray, options: AESOptions = {}): CipherResult {
|
|
62
|
+
const mode = options.mode || CBC;
|
|
63
|
+
const padding = options.padding || Pkcs7;
|
|
64
|
+
|
|
65
|
+
const keyBytes = key.toUint8Array();
|
|
66
|
+
const algorithm = getAlgorithm(keyBytes, mode);
|
|
67
|
+
const ciphertextBytes = ciphertext.toUint8Array();
|
|
68
|
+
|
|
69
|
+
// Get IV (for CBC mode)
|
|
70
|
+
let iv: Uint8Array | null = null;
|
|
71
|
+
if (mode === CBC) {
|
|
72
|
+
iv = options.iv ? options.iv.toUint8Array() : new Uint8Array(16);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const decipher = createDecipheriv(algorithm, keyBytes, iv);
|
|
76
|
+
decipher.setAutoPadding(false); // We handle padding ourselves
|
|
77
|
+
|
|
78
|
+
const decrypted = Buffer.concat([decipher.update(ciphertextBytes), decipher.final()]);
|
|
79
|
+
const result = WordArray.fromUint8Array(new Uint8Array(decrypted));
|
|
80
|
+
|
|
81
|
+
padding.unpad(result);
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
ciphertext: result,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const AES: AESStatic = {
|
|
89
|
+
encrypt,
|
|
90
|
+
decrypt,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export default AES;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { WordArray } from "./word-array.js";
|
|
3
|
+
|
|
4
|
+
export function MD5(message: WordArray | string): WordArray {
|
|
5
|
+
const data = typeof message === "string" ? message : message.toUint8Array();
|
|
6
|
+
const hash = createHash("md5").update(data).digest();
|
|
7
|
+
return WordArray.fromUint8Array(hash);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default MD5;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { WordArray } from "./word-array.js";
|
|
2
|
+
|
|
3
|
+
export interface Padding {
|
|
4
|
+
pad(data: WordArray, blockSize: number): void;
|
|
5
|
+
unpad(data: WordArray): void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const Pkcs7: Padding = {
|
|
9
|
+
pad(data: WordArray, blockSize: number): void {
|
|
10
|
+
const blockSizeBytes = blockSize * 4;
|
|
11
|
+
const nPaddingBytes = blockSizeBytes - (data.sigBytes % blockSizeBytes);
|
|
12
|
+
const paddingWord =
|
|
13
|
+
(nPaddingBytes << 24) | (nPaddingBytes << 16) | (nPaddingBytes << 8) | nPaddingBytes;
|
|
14
|
+
|
|
15
|
+
const paddingWords: number[] = [];
|
|
16
|
+
for (let i = 0; i < nPaddingBytes; i += 4) {
|
|
17
|
+
paddingWords.push(paddingWord);
|
|
18
|
+
}
|
|
19
|
+
const padding = new WordArray(paddingWords, nPaddingBytes);
|
|
20
|
+
data.concat(padding);
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
unpad(data: WordArray): void {
|
|
24
|
+
const nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff;
|
|
25
|
+
data.sigBytes -= nPaddingBytes;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const NoPadding: Padding = {
|
|
30
|
+
pad(_data: WordArray, _blockSize: number): void {
|
|
31
|
+
// No padding
|
|
32
|
+
},
|
|
33
|
+
unpad(_data: WordArray): void {
|
|
34
|
+
// No unpadding
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { WordArray } from "./word-array.js";
|
|
2
|
+
|
|
3
|
+
export interface CipherResult {
|
|
4
|
+
ciphertext: WordArray;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface RC4Static {
|
|
8
|
+
encrypt(message: WordArray, key: WordArray): CipherResult;
|
|
9
|
+
decrypt(ciphertext: WordArray, key: WordArray): CipherResult;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function rc4Process(data: Uint8Array, key: Uint8Array): Uint8Array {
|
|
13
|
+
// Key Scheduling Algorithm (KSA)
|
|
14
|
+
const S = new Uint8Array(256);
|
|
15
|
+
for (let i = 0; i < 256; i++) {
|
|
16
|
+
S[i] = i;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let j = 0;
|
|
20
|
+
for (let i = 0; i < 256; i++) {
|
|
21
|
+
j = (j + S[i] + key[i % key.length]) & 0xff;
|
|
22
|
+
// Swap S[i] and S[j]
|
|
23
|
+
const temp = S[i];
|
|
24
|
+
S[i] = S[j];
|
|
25
|
+
S[j] = temp;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Pseudo-Random Generation Algorithm (PRGA)
|
|
29
|
+
const result = new Uint8Array(data.length);
|
|
30
|
+
let i = 0;
|
|
31
|
+
j = 0;
|
|
32
|
+
for (let k = 0; k < data.length; k++) {
|
|
33
|
+
i = (i + 1) & 0xff;
|
|
34
|
+
j = (j + S[i]) & 0xff;
|
|
35
|
+
// Swap S[i] and S[j]
|
|
36
|
+
const temp = S[i];
|
|
37
|
+
S[i] = S[j];
|
|
38
|
+
S[j] = temp;
|
|
39
|
+
// Generate keystream byte and XOR with data
|
|
40
|
+
result[k] = data[k] ^ S[(S[i] + S[j]) & 0xff];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function encrypt(message: WordArray, key: WordArray): CipherResult {
|
|
47
|
+
const keyBytes = key.toUint8Array();
|
|
48
|
+
const messageBytes = message.toUint8Array();
|
|
49
|
+
const encrypted = rc4Process(messageBytes, keyBytes);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
ciphertext: WordArray.fromUint8Array(encrypted),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function decrypt(ciphertext: WordArray, key: WordArray): CipherResult {
|
|
57
|
+
return encrypt(ciphertext, key);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const RC4: RC4Static = {
|
|
61
|
+
encrypt,
|
|
62
|
+
decrypt,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default RC4;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { WordArray } from "./word-array.js";
|
|
3
|
+
|
|
4
|
+
export function SHA256(message: WordArray | string): WordArray {
|
|
5
|
+
const data = typeof message === "string" ? message : message.toUint8Array();
|
|
6
|
+
const hash = createHash("sha256").update(data).digest();
|
|
7
|
+
return WordArray.fromUint8Array(hash);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default SHA256;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export interface Encoder {
|
|
4
|
+
stringify(wordArray: WordArray): string;
|
|
5
|
+
parse(str: string): WordArray;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class WordArray {
|
|
9
|
+
words: number[];
|
|
10
|
+
sigBytes: number;
|
|
11
|
+
|
|
12
|
+
constructor(words?: number[], sigBytes?: number) {
|
|
13
|
+
this.words = words || [];
|
|
14
|
+
this.sigBytes = sigBytes !== undefined ? sigBytes : this.words.length * 4;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static create(words?: number[], sigBytes?: number): WordArray {
|
|
18
|
+
return new WordArray(words, sigBytes);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a word array filled with random bytes.
|
|
23
|
+
*/
|
|
24
|
+
static random(nBytes: number): WordArray {
|
|
25
|
+
const bytes = randomBytes(nBytes);
|
|
26
|
+
return WordArray.fromUint8Array(bytes);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Create WordArray from Uint8Array
|
|
31
|
+
*/
|
|
32
|
+
static fromUint8Array(bytes: Uint8Array): WordArray {
|
|
33
|
+
const words: number[] = [];
|
|
34
|
+
for (let i = 0; i < bytes.length; i += 4) {
|
|
35
|
+
words.push(
|
|
36
|
+
((bytes[i] || 0) << 24) |
|
|
37
|
+
((bytes[i + 1] || 0) << 16) |
|
|
38
|
+
((bytes[i + 2] || 0) << 8) |
|
|
39
|
+
(bytes[i + 3] || 0),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return new WordArray(words, bytes.length);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Convert WordArray to Uint8Array
|
|
47
|
+
*/
|
|
48
|
+
toUint8Array(): Uint8Array {
|
|
49
|
+
const bytes = new Uint8Array(this.sigBytes);
|
|
50
|
+
for (let i = 0; i < this.sigBytes; i++) {
|
|
51
|
+
bytes[i] = (this.words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
|
|
52
|
+
}
|
|
53
|
+
return bytes;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
toString(encoder?: Encoder): string {
|
|
57
|
+
return (encoder || Hex).stringify(this);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
concat(wordArray: WordArray): WordArray {
|
|
61
|
+
const thisWords = this.words;
|
|
62
|
+
const thatWords = wordArray.words;
|
|
63
|
+
const thisSigBytes = this.sigBytes;
|
|
64
|
+
const thatSigBytes = wordArray.sigBytes;
|
|
65
|
+
|
|
66
|
+
this.clamp();
|
|
67
|
+
|
|
68
|
+
if (thisSigBytes % 4) {
|
|
69
|
+
for (let i = 0; i < thatSigBytes; i++) {
|
|
70
|
+
const thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
|
|
71
|
+
thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
for (let j = 0; j < thatSigBytes; j += 4) {
|
|
75
|
+
thisWords[(thisSigBytes + j) >>> 2] = thatWords[j >>> 2];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
this.sigBytes += thatSigBytes;
|
|
79
|
+
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
clamp(): void {
|
|
84
|
+
const words = this.words;
|
|
85
|
+
const sigBytes = this.sigBytes;
|
|
86
|
+
|
|
87
|
+
words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8);
|
|
88
|
+
words.length = Math.ceil(sigBytes / 4);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
clone(): WordArray {
|
|
92
|
+
return new WordArray(this.words.slice(0), this.sigBytes);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const Hex: Encoder = {
|
|
97
|
+
stringify(wordArray: WordArray): string {
|
|
98
|
+
const words = wordArray.words;
|
|
99
|
+
const sigBytes = wordArray.sigBytes;
|
|
100
|
+
const hexChars: string[] = [];
|
|
101
|
+
|
|
102
|
+
for (let i = 0; i < sigBytes; i++) {
|
|
103
|
+
const bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
|
|
104
|
+
hexChars.push((bite >>> 4).toString(16));
|
|
105
|
+
hexChars.push((bite & 0x0f).toString(16));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return hexChars.join("");
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
parse(hexStr: string): WordArray {
|
|
112
|
+
const hexStrLength = hexStr.length;
|
|
113
|
+
const words: number[] = [];
|
|
114
|
+
|
|
115
|
+
for (let i = 0; i < hexStrLength; i += 2) {
|
|
116
|
+
words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return new WordArray(words, hexStrLength / 2);
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const Latin1: Encoder = {
|
|
124
|
+
stringify(wordArray: WordArray): string {
|
|
125
|
+
const words = wordArray.words;
|
|
126
|
+
const sigBytes = wordArray.sigBytes;
|
|
127
|
+
const chars: string[] = [];
|
|
128
|
+
|
|
129
|
+
for (let i = 0; i < sigBytes; i++) {
|
|
130
|
+
const bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
|
|
131
|
+
chars.push(String.fromCharCode(bite));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return chars.join("");
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
parse(latin1Str: string): WordArray {
|
|
138
|
+
const length = latin1Str.length;
|
|
139
|
+
const words: number[] = [];
|
|
140
|
+
|
|
141
|
+
for (let i = 0; i < length; i++) {
|
|
142
|
+
words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return new WordArray(words, length);
|
|
146
|
+
},
|
|
147
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
base64,
|
|
5
|
+
decodeFromBase64,
|
|
6
|
+
decodeFromBase64DataUri,
|
|
7
|
+
encodeToBase64,
|
|
8
|
+
} from "../src/base64.js";
|
|
9
|
+
|
|
10
|
+
describe("encodeToBase64", () => {
|
|
11
|
+
test("matches Node.js Buffer encoding for simple data", () => {
|
|
12
|
+
const data = new Uint8Array([72, 101, 108, 108, 111]); // "Hello"
|
|
13
|
+
const result = encodeToBase64(data);
|
|
14
|
+
const expected = Buffer.from(data).toString("base64");
|
|
15
|
+
expect(result).toBe(expected);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("matches Node.js Buffer encoding for binary data", () => {
|
|
19
|
+
const data = new Uint8Array([0, 1, 2, 255, 254, 253, 128, 64, 32]);
|
|
20
|
+
const result = encodeToBase64(data);
|
|
21
|
+
const expected = Buffer.from(data).toString("base64");
|
|
22
|
+
expect(result).toBe(expected);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("matches Node.js Buffer encoding with padding (1 byte)", () => {
|
|
26
|
+
const data = new Uint8Array([65]); // requires == padding
|
|
27
|
+
const result = encodeToBase64(data);
|
|
28
|
+
const expected = Buffer.from(data).toString("base64");
|
|
29
|
+
expect(result).toBe(expected);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("matches Node.js Buffer encoding with padding (2 bytes)", () => {
|
|
33
|
+
const data = new Uint8Array([65, 66]); // requires = padding
|
|
34
|
+
const result = encodeToBase64(data);
|
|
35
|
+
const expected = Buffer.from(data).toString("base64");
|
|
36
|
+
expect(result).toBe(expected);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("matches Node.js Buffer encoding with no padding (3 bytes)", () => {
|
|
40
|
+
const data = new Uint8Array([65, 66, 67]); // no padding needed
|
|
41
|
+
const result = encodeToBase64(data);
|
|
42
|
+
const expected = Buffer.from(data).toString("base64");
|
|
43
|
+
expect(result).toBe(expected);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("matches Node.js Buffer encoding for empty data", () => {
|
|
47
|
+
const data = new Uint8Array([]);
|
|
48
|
+
const result = encodeToBase64(data);
|
|
49
|
+
const expected = Buffer.from(data).toString("base64");
|
|
50
|
+
expect(result).toBe(expected);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("matches Node.js Buffer encoding for large data", () => {
|
|
54
|
+
const data = new Uint8Array(1000);
|
|
55
|
+
for (let i = 0; i < data.length; i++) data[i] = i % 256;
|
|
56
|
+
const result = encodeToBase64(data);
|
|
57
|
+
const expected = Buffer.from(data).toString("base64");
|
|
58
|
+
expect(result).toBe(expected);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("decodeFromBase64", () => {
|
|
63
|
+
test("matches Node.js Buffer decoding for simple data", () => {
|
|
64
|
+
const base64 = "SGVsbG8="; // "Hello"
|
|
65
|
+
const result = decodeFromBase64(base64);
|
|
66
|
+
const expected = new Uint8Array(Buffer.from(base64, "base64"));
|
|
67
|
+
expect(result).toEqual(expected);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("matches Node.js Buffer decoding for binary data", () => {
|
|
71
|
+
const base64 = "AAEC//79gEAg";
|
|
72
|
+
const result = decodeFromBase64(base64);
|
|
73
|
+
const expected = new Uint8Array(Buffer.from(base64, "base64"));
|
|
74
|
+
expect(result).toEqual(expected);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("matches Node.js Buffer decoding with double padding", () => {
|
|
78
|
+
const base64 = "QQ==";
|
|
79
|
+
const result = decodeFromBase64(base64);
|
|
80
|
+
const expected = new Uint8Array(Buffer.from(base64, "base64"));
|
|
81
|
+
expect(result).toEqual(expected);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("matches Node.js Buffer decoding with single padding", () => {
|
|
85
|
+
const base64 = "QUI=";
|
|
86
|
+
const result = decodeFromBase64(base64);
|
|
87
|
+
const expected = new Uint8Array(Buffer.from(base64, "base64"));
|
|
88
|
+
expect(result).toEqual(expected);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("matches Node.js Buffer decoding with no padding", () => {
|
|
92
|
+
const base64 = "QUJD";
|
|
93
|
+
const result = decodeFromBase64(base64);
|
|
94
|
+
const expected = new Uint8Array(Buffer.from(base64, "base64"));
|
|
95
|
+
expect(result).toEqual(expected);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("matches Node.js Buffer decoding for empty string", () => {
|
|
99
|
+
const base64 = "";
|
|
100
|
+
const result = decodeFromBase64(base64);
|
|
101
|
+
const expected = new Uint8Array(Buffer.from(base64, "base64"));
|
|
102
|
+
expect(result).toEqual(expected);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("roundtrip encode/decode matches original data", () => {
|
|
106
|
+
const original = new Uint8Array([0, 127, 255, 1, 254, 128]);
|
|
107
|
+
const encoded = encodeToBase64(original);
|
|
108
|
+
const decoded = decodeFromBase64(encoded);
|
|
109
|
+
expect(decoded).toEqual(original);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("decodeFromBase64DataUri", () => {
|
|
114
|
+
const testData = new Uint8Array([37, 80, 68, 70]); // %PDF
|
|
115
|
+
const base64String = Buffer.from(testData).toString("base64");
|
|
116
|
+
|
|
117
|
+
test("can decode plain base64 strings", () => {
|
|
118
|
+
const result = decodeFromBase64DataUri(base64String);
|
|
119
|
+
expect(result).toEqual(testData);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("can decode complete data URIs", () => {
|
|
123
|
+
const uri = `data:application/pdf;base64,${base64String}`;
|
|
124
|
+
const result = decodeFromBase64DataUri(uri);
|
|
125
|
+
expect(result).toEqual(testData);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("can decode data URI without base64 marker", () => {
|
|
129
|
+
const uri = `data:application/pdf;,${base64String}`;
|
|
130
|
+
const result = decodeFromBase64DataUri(uri);
|
|
131
|
+
expect(result).toEqual(testData);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("can decode data URI without mime type", () => {
|
|
135
|
+
const uri = `data:;,${base64String}`;
|
|
136
|
+
const result = decodeFromBase64DataUri(uri);
|
|
137
|
+
expect(result).toEqual(testData);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("can decode minimal data URI", () => {
|
|
141
|
+
const uri = `,${base64String}`;
|
|
142
|
+
const result = decodeFromBase64DataUri(uri);
|
|
143
|
+
expect(result).toEqual(testData);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("throws an error for invalid input", () => {
|
|
147
|
+
const uri = {} as any;
|
|
148
|
+
expect(() => decodeFromBase64DataUri(uri)).toThrow();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("encode/decode (base64-arraybuffer compatible)", () => {
|
|
153
|
+
test("encode matches Node.js Buffer encoding", () => {
|
|
154
|
+
const data = new Uint8Array([72, 101, 108, 108, 111]).buffer; // "Hello"
|
|
155
|
+
const result = base64.encode(data);
|
|
156
|
+
const expected = Buffer.from(new Uint8Array(data)).toString("base64");
|
|
157
|
+
expect(result).toBe(expected);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("decode returns ArrayBuffer", () => {
|
|
161
|
+
const encoded = "SGVsbG8=";
|
|
162
|
+
const result = base64.decode(encoded);
|
|
163
|
+
expect(result).toBeInstanceOf(ArrayBuffer);
|
|
164
|
+
expect(new Uint8Array(result)).toEqual(new Uint8Array(Buffer.from(encoded, "base64")));
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("roundtrip encode/decode with ArrayBuffer", () => {
|
|
168
|
+
const original = new Uint8Array([0, 127, 255, 1, 254, 128]).buffer;
|
|
169
|
+
const encoded = base64.encode(original);
|
|
170
|
+
const decoded = base64.decode(encoded);
|
|
171
|
+
expect(new Uint8Array(decoded)).toEqual(new Uint8Array(original));
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
;
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
�The quick brown fox jumps over the lazy dog
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|