@atproto/lex-data 0.1.2 → 0.1.4

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/blob.d.ts +2 -2
  3. package/dist/blob.d.ts.map +1 -1
  4. package/dist/blob.js +1 -1
  5. package/dist/blob.js.map +1 -1
  6. package/dist/lex-equals.d.ts +1 -1
  7. package/dist/lex-equals.d.ts.map +1 -1
  8. package/dist/lex-equals.js.map +1 -1
  9. package/dist/lex.d.ts +1 -1
  10. package/dist/lex.d.ts.map +1 -1
  11. package/dist/lex.js.map +1 -1
  12. package/dist/lib/nodejs-buffer.js.map +1 -1
  13. package/dist/uint8array-from-base64.d.ts +1 -1
  14. package/dist/uint8array-from-base64.d.ts.map +1 -1
  15. package/dist/uint8array-from-base64.js.map +1 -1
  16. package/dist/uint8array-to-base64.d.ts +1 -1
  17. package/dist/uint8array-to-base64.d.ts.map +1 -1
  18. package/dist/uint8array-to-base64.js.map +1 -1
  19. package/dist/uint8array.d.ts +1 -1
  20. package/dist/uint8array.d.ts.map +1 -1
  21. package/dist/uint8array.js.map +1 -1
  22. package/dist/utf8-from-base64.d.ts +1 -1
  23. package/dist/utf8-from-base64.d.ts.map +1 -1
  24. package/dist/utf8-from-base64.js.map +1 -1
  25. package/dist/utf8-to-base64.d.ts +1 -1
  26. package/dist/utf8-to-base64.d.ts.map +1 -1
  27. package/dist/utf8-to-base64.js.map +1 -1
  28. package/dist/utf8.d.ts +1 -1
  29. package/dist/utf8.d.ts.map +1 -1
  30. package/dist/utf8.js.map +1 -1
  31. package/package.json +4 -8
  32. package/src/blob.test.ts +0 -405
  33. package/src/blob.ts +0 -478
  34. package/src/cid-implementation.test.ts +0 -129
  35. package/src/cid.test.ts +0 -350
  36. package/src/cid.ts +0 -603
  37. package/src/core-js.d.ts +0 -2
  38. package/src/index.ts +0 -8
  39. package/src/lex-equals.test.ts +0 -183
  40. package/src/lex-equals.ts +0 -123
  41. package/src/lex-error.test.ts +0 -54
  42. package/src/lex-error.ts +0 -83
  43. package/src/lex.test.ts +0 -279
  44. package/src/lex.ts +0 -253
  45. package/src/lib/nodejs-buffer.ts +0 -46
  46. package/src/lib/util.test.ts +0 -49
  47. package/src/lib/util.ts +0 -7
  48. package/src/object.test.ts +0 -80
  49. package/src/object.ts +0 -83
  50. package/src/uint8array-base64.ts +0 -2
  51. package/src/uint8array-concat.test.ts +0 -197
  52. package/src/uint8array-concat.ts +0 -25
  53. package/src/uint8array-from-base64.test.ts +0 -130
  54. package/src/uint8array-from-base64.ts +0 -98
  55. package/src/uint8array-to-base64.test.ts +0 -170
  56. package/src/uint8array-to-base64.ts +0 -55
  57. package/src/uint8array.test.ts +0 -503
  58. package/src/uint8array.ts +0 -197
  59. package/src/utf8-from-base64.test.ts +0 -39
  60. package/src/utf8-from-base64.ts +0 -23
  61. package/src/utf8-from-bytes.test.ts +0 -43
  62. package/src/utf8-from-bytes.ts +0 -21
  63. package/src/utf8-grapheme-len.test.ts +0 -38
  64. package/src/utf8-grapheme-len.ts +0 -21
  65. package/src/utf8-len.test.ts +0 -21
  66. package/src/utf8-len.ts +0 -51
  67. package/src/utf8-to-base64.test.ts +0 -35
  68. package/src/utf8-to-base64.ts +0 -22
  69. package/src/utf8.ts +0 -128
  70. package/tsconfig.build.json +0 -12
  71. package/tsconfig.json +0 -7
  72. package/tsconfig.tests.json +0 -8
package/src/object.ts DELETED
@@ -1,83 +0,0 @@
1
- /**
2
- * Checks whether the input is an object (not null).
3
- *
4
- * Returns true for any non-null value with typeof 'object', including
5
- * arrays, plain objects, class instances, etc.
6
- *
7
- * @param input - The value to check
8
- * @returns `true` if the input is an object (not null)
9
- *
10
- * @example
11
- * ```typescript
12
- * import { isObject } from '@atproto/lex-data'
13
- *
14
- * isObject({}) // true
15
- * isObject([1, 2, 3]) // true
16
- * isObject(new Date()) // true
17
- * isObject(null) // false
18
- * isObject('string') // false
19
- * ```
20
- */
21
- export function isObject(input: unknown): input is object {
22
- return input != null && typeof input === 'object'
23
- }
24
-
25
- const ObjectProto = Object.prototype
26
- const ObjectToString = Object.prototype.toString
27
-
28
- /**
29
- * Checks whether the input is a plain object.
30
- *
31
- * A plain object is an object (not null) whose prototype is either null
32
- * or `Object.prototype`. This excludes arrays, class instances, and other
33
- * special objects.
34
- *
35
- * @param input - The value to check
36
- * @returns `true` if the input is a plain object
37
- *
38
- * @example
39
- * ```typescript
40
- * import { isPlainObject } from '@atproto/lex-data'
41
- *
42
- * isPlainObject({}) // true
43
- * isPlainObject({ a: 1 }) // true
44
- * isPlainObject(Object.create(null)) // true
45
- * isPlainObject([1, 2, 3]) // false
46
- * isPlainObject(new Date()) // false
47
- * isPlainObject(null) // false
48
- * ```
49
- */
50
- export function isPlainObject(input: unknown) {
51
- return isObject(input) && isPlainProto(input)
52
- }
53
-
54
- /**
55
- * Checks whether the prototype of an object is plain (null or Object.prototype).
56
- *
57
- * This is useful for checking if an object is a plain object without
58
- * checking that it's non-null first (the null check is already done).
59
- *
60
- * @param input - The object to check (must be non-null)
61
- * @returns `true` if the object's prototype is plain
62
- *
63
- * @example
64
- * ```typescript
65
- * import { isPlainProto } from '@atproto/lex-data'
66
- *
67
- * isPlainProto({}) // true
68
- * isPlainProto(Object.create(null)) // true
69
- * isPlainProto([1, 2, 3]) // false (Array.prototype)
70
- * isPlainProto(new Date()) // false (Date.prototype)
71
- * ```
72
- */
73
- export function isPlainProto(input: object): input is Record<string, unknown> {
74
- const proto = Object.getPrototypeOf(input)
75
- if (proto === null) return true
76
- return (
77
- (proto === ObjectProto ||
78
- // Needed to support NodeJS's `runInNewContext` which produces objects
79
- // with a different prototype
80
- Object.getPrototypeOf(proto) === null) &&
81
- ObjectToString.call(input) === '[object Object]'
82
- )
83
- }
@@ -1,2 +0,0 @@
1
- /** @default 'base64' */
2
- export type Base64Alphabet = 'base64' | 'base64url'
@@ -1,197 +0,0 @@
1
- import { assert, describe, expect, it } from 'vitest'
2
- import { ui8ConcatNode, ui8ConcatPonyfill } from './uint8array-concat.js'
3
- import { ui8Equals } from './uint8array.js'
4
-
5
- for (const ui8Concat of [ui8ConcatNode, ui8ConcatPonyfill] as const) {
6
- // Tests should run in NodeJS where implementations are available.
7
- assert(ui8Concat, 'ui8Concat implementation should not be undefined')
8
-
9
- describe(ui8Concat.name, () => {
10
- describe('empty array', () => {
11
- it('returns empty Uint8Array when given empty array', () => {
12
- const result = ui8Concat([])
13
- expect(result).toBeInstanceOf(Uint8Array)
14
- expect(result.length).toBe(0)
15
- })
16
- })
17
-
18
- describe('single array', () => {
19
- it('returns copy of single Uint8Array', () => {
20
- const input = new Uint8Array([1, 2, 3])
21
- const result = ui8Concat([input])
22
- expect(result).toBeInstanceOf(Uint8Array)
23
- expect(ui8Equals(result, input)).toBe(true)
24
- })
25
-
26
- it('returns copy of single empty Uint8Array', () => {
27
- const input = new Uint8Array(0)
28
- const result = ui8Concat([input])
29
- expect(result).toBeInstanceOf(Uint8Array)
30
- expect(result.length).toBe(0)
31
- })
32
- })
33
-
34
- describe('multiple arrays', () => {
35
- it('concatenates two arrays', () => {
36
- const a = new Uint8Array([1, 2, 3])
37
- const b = new Uint8Array([4, 5, 6])
38
- const result = ui8Concat([a, b])
39
- expect(result).toBeInstanceOf(Uint8Array)
40
- expect(ui8Equals(result, new Uint8Array([1, 2, 3, 4, 5, 6]))).toBe(true)
41
- })
42
-
43
- it('concatenates three arrays', () => {
44
- const a = new Uint8Array([1, 2])
45
- const b = new Uint8Array([3, 4])
46
- const c = new Uint8Array([5, 6])
47
- const result = ui8Concat([a, b, c])
48
- expect(result).toBeInstanceOf(Uint8Array)
49
- expect(ui8Equals(result, new Uint8Array([1, 2, 3, 4, 5, 6]))).toBe(true)
50
- })
51
-
52
- it('concatenates many arrays', () => {
53
- const arrays = [
54
- new Uint8Array([0x00]),
55
- new Uint8Array([0x01, 0x02]),
56
- new Uint8Array([0x03, 0x04, 0x05]),
57
- new Uint8Array([0x06, 0x07, 0x08, 0x09]),
58
- new Uint8Array([0x0a]),
59
- ]
60
- const result = ui8Concat(arrays)
61
- expect(result).toBeInstanceOf(Uint8Array)
62
- expect(
63
- ui8Equals(
64
- result,
65
- new Uint8Array([
66
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
67
- ]),
68
- ),
69
- ).toBe(true)
70
- })
71
- })
72
-
73
- describe('arrays with different lengths', () => {
74
- it('concatenates arrays of varying lengths', () => {
75
- const a = new Uint8Array([1])
76
- const b = new Uint8Array([2, 3, 4, 5, 6])
77
- const c = new Uint8Array([7, 8])
78
- const result = ui8Concat([a, b, c])
79
- expect(result).toBeInstanceOf(Uint8Array)
80
- expect(
81
- ui8Equals(result, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])),
82
- ).toBe(true)
83
- })
84
-
85
- it('handles large arrays', () => {
86
- const size = 10000
87
- const a = new Uint8Array(size).fill(0xaa)
88
- const b = new Uint8Array(size).fill(0xbb)
89
- const result = ui8Concat([a, b])
90
- expect(result).toBeInstanceOf(Uint8Array)
91
- expect(result.length).toBe(size * 2)
92
- // Check first array portion
93
- for (let i = 0; i < size; i++) {
94
- expect(result[i]).toBe(0xaa)
95
- }
96
- // Check second array portion
97
- for (let i = size; i < size * 2; i++) {
98
- expect(result[i]).toBe(0xbb)
99
- }
100
- })
101
- })
102
-
103
- describe('empty Uint8Arrays in input', () => {
104
- it('handles empty array at the beginning', () => {
105
- const a = new Uint8Array(0)
106
- const b = new Uint8Array([1, 2, 3])
107
- const result = ui8Concat([a, b])
108
- expect(result).toBeInstanceOf(Uint8Array)
109
- expect(ui8Equals(result, new Uint8Array([1, 2, 3]))).toBe(true)
110
- })
111
-
112
- it('handles empty array in the middle', () => {
113
- const a = new Uint8Array([1, 2])
114
- const b = new Uint8Array(0)
115
- const c = new Uint8Array([3, 4])
116
- const result = ui8Concat([a, b, c])
117
- expect(result).toBeInstanceOf(Uint8Array)
118
- expect(ui8Equals(result, new Uint8Array([1, 2, 3, 4]))).toBe(true)
119
- })
120
-
121
- it('handles empty array at the end', () => {
122
- const a = new Uint8Array([1, 2, 3])
123
- const b = new Uint8Array(0)
124
- const result = ui8Concat([a, b])
125
- expect(result).toBeInstanceOf(Uint8Array)
126
- expect(ui8Equals(result, new Uint8Array([1, 2, 3]))).toBe(true)
127
- })
128
-
129
- it('handles multiple empty arrays', () => {
130
- const a = new Uint8Array(0)
131
- const b = new Uint8Array([1])
132
- const c = new Uint8Array(0)
133
- const d = new Uint8Array([2])
134
- const e = new Uint8Array(0)
135
- const result = ui8Concat([a, b, c, d, e])
136
- expect(result).toBeInstanceOf(Uint8Array)
137
- expect(ui8Equals(result, new Uint8Array([1, 2]))).toBe(true)
138
- })
139
-
140
- it('handles all empty arrays', () => {
141
- const a = new Uint8Array(0)
142
- const b = new Uint8Array(0)
143
- const c = new Uint8Array(0)
144
- const result = ui8Concat([a, b, c])
145
- expect(result).toBeInstanceOf(Uint8Array)
146
- expect(result.length).toBe(0)
147
- })
148
- })
149
-
150
- describe('byte value preservation', () => {
151
- it('preserves all byte values from 0x00 to 0xff', () => {
152
- const allBytes = new Uint8Array(256)
153
- for (let i = 0; i < 256; i++) {
154
- allBytes[i] = i
155
- }
156
- const result = ui8Concat([allBytes])
157
- expect(result).toBeInstanceOf(Uint8Array)
158
- expect(ui8Equals(result, allBytes)).toBe(true)
159
- })
160
-
161
- it('preserves boundary byte values in concatenation', () => {
162
- const a = new Uint8Array([0x00, 0x01, 0x7f])
163
- const b = new Uint8Array([0x80, 0xfe, 0xff])
164
- const result = ui8Concat([a, b])
165
- expect(result).toBeInstanceOf(Uint8Array)
166
- expect(
167
- ui8Equals(
168
- result,
169
- new Uint8Array([0x00, 0x01, 0x7f, 0x80, 0xfe, 0xff]),
170
- ),
171
- ).toBe(true)
172
- })
173
- })
174
-
175
- describe('subarray handling', () => {
176
- it('correctly concatenates subarrays', () => {
177
- const fullArray = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
178
- const sub1 = fullArray.subarray(0, 3) // [0, 1, 2]
179
- const sub2 = fullArray.subarray(5, 8) // [5, 6, 7]
180
- const result = ui8Concat([sub1, sub2])
181
- expect(result).toBeInstanceOf(Uint8Array)
182
- expect(ui8Equals(result, new Uint8Array([0, 1, 2, 5, 6, 7]))).toBe(true)
183
- })
184
-
185
- it('correctly concatenates subarray with regular array', () => {
186
- const fullArray = new Uint8Array([10, 20, 30, 40, 50])
187
- const sub = fullArray.subarray(1, 4) // [20, 30, 40]
188
- const regular = new Uint8Array([100, 200])
189
- const result = ui8Concat([sub, regular])
190
- expect(result).toBeInstanceOf(Uint8Array)
191
- expect(ui8Equals(result, new Uint8Array([20, 30, 40, 100, 200]))).toBe(
192
- true,
193
- )
194
- })
195
- })
196
- })
197
- }
@@ -1,25 +0,0 @@
1
- import { NodeJSBuffer } from './lib/nodejs-buffer.js'
2
-
3
- const Buffer = NodeJSBuffer
4
-
5
- export const ui8ConcatNode = Buffer
6
- ? function ui8ConcatNode(
7
- array: readonly Uint8Array[],
8
- ): Uint8Array<ArrayBuffer> {
9
- return Buffer.concat(array)
10
- }
11
- : /* v8 ignore next -- @preserve */ null
12
-
13
- export function ui8ConcatPonyfill(
14
- array: readonly Uint8Array[],
15
- ): Uint8Array<ArrayBuffer> {
16
- let totalLength = 0
17
- for (const arr of array) totalLength += arr.length
18
- const result = new Uint8Array(totalLength)
19
- let offset = 0
20
- for (const arr of array) {
21
- result.set(arr, offset)
22
- offset += arr.length
23
- }
24
- return result
25
- }
@@ -1,130 +0,0 @@
1
- import 'core-js/es/typed-array/from-base64.js'
2
- import 'core-js/es/typed-array/to-base64.js'
3
-
4
- import { assert, describe, expect, it } from 'vitest'
5
- import {
6
- fromBase64Native,
7
- fromBase64Node,
8
- fromBase64Ponyfill,
9
- } from './uint8array-from-base64.js'
10
- import { ui8Equals } from './uint8array.js'
11
-
12
- // @NOTE This test suite relies on the NodeJS Buffer implementation to generate
13
- // valid base64 strings for testing.
14
-
15
- // @NOTE b64 needs a test suite because fromBase64 implementations differ in
16
- // their behavior when encountering invalid base64 strings. This is not the case
17
- // for toBase64, which is straightforward and has no edge cases.
18
-
19
- for (const fromBase64 of [
20
- fromBase64Native,
21
- fromBase64Node,
22
- fromBase64Ponyfill,
23
- ] as const) {
24
- // Tests should run in NodeJS where implementations are either available or
25
- // polyfilled (see core-js imports above).
26
- assert(fromBase64 !== null, 'fromBase64 implementation should not be null')
27
-
28
- describe(fromBase64.name, () => {
29
- describe('valid base64 strings', () => {
30
- it('decodes empty string', () => {
31
- const decoded = fromBase64('')
32
- expect(decoded).toBeInstanceOf(Uint8Array)
33
- expect(decoded.length).toBe(0)
34
- })
35
-
36
- it('decodes 10MB', () => {
37
- const bytes = Buffer.allocUnsafe(10_000_000).fill('🐩')
38
- const encoded = bytes.toString('base64')
39
- const decoded = fromBase64(encoded)
40
- expect(decoded).toBeInstanceOf(Uint8Array)
41
- expect(ui8Equals(decoded, bytes)).toBe(true)
42
- })
43
-
44
- for (const buffer of [
45
- Buffer.from(''),
46
- Buffer.from('\0\0'),
47
- Buffer.from('\0\0\0'),
48
- Buffer.from('\0\0\0\0'),
49
- Buffer.from('__'),
50
- Buffer.from('é'),
51
- Buffer.from('àç'),
52
- Buffer.from('\0éàç'),
53
- Buffer.from('```'),
54
- Buffer.from('aaa'),
55
- Buffer.from('Hello, World!'),
56
- Buffer.from('😀😃😄😁😆😅😂🤣😊😇'),
57
- Buffer.from('👩‍💻👨‍💻👩‍🔬👨‍🔬👩‍🚀👨‍🚀'),
58
- Buffer.from('🌍🌎🌏🌐🪐🌟✨⚡🔥💧'),
59
- Buffer.from(new Uint8Array([0xfb, 0xff, 0xbf])),
60
- Buffer.from(new Uint8Array([0xfb, 0xff, 0xbf])),
61
- Buffer.from(new Uint8Array([0x4d])),
62
- Buffer.from(new Uint8Array([0x4d, 0x61])),
63
- Buffer.from(new Uint8Array([0x4d, 0x61, 0x6e])),
64
- Buffer.from(new Uint8Array([0x4d])),
65
- Buffer.from(new Uint8Array([0x4d, 0x61])),
66
- Buffer.from(new Uint8Array([0x00, 0x4d, 0x61, 0x6e, 0x00])),
67
- ]) {
68
- const base64 = buffer.toString('base64')
69
- const base64Unpadded = base64.replace(/=+$/, '')
70
- const base64url = buffer.toString('base64url') // No padding in base64url
71
-
72
- it(`decodes ${JSON.stringify(base64)}`, () => {
73
- const decoded = fromBase64(base64)
74
- expect(decoded).toBeInstanceOf(Uint8Array)
75
- expect(ui8Equals(decoded, buffer)).toBe(true)
76
- })
77
-
78
- it(`decodes ${JSON.stringify(base64url)} (base64url)`, () => {
79
- const decoded = fromBase64(base64url, 'base64url')
80
- expect(decoded).toBeInstanceOf(Uint8Array)
81
- expect(ui8Equals(decoded, buffer)).toBe(true)
82
- })
83
-
84
- if (base64 !== base64Unpadded) {
85
- it(`decodes ${JSON.stringify(base64Unpadded)} (unpadded)`, () => {
86
- const decoded = fromBase64(base64Unpadded)
87
- expect(decoded).toBeInstanceOf(Uint8Array)
88
- expect(ui8Equals(decoded, buffer)).toBe(true)
89
- })
90
- }
91
- }
92
- })
93
-
94
- describe('invalid base64 strings', () => {
95
- for (const invalidB64 of [
96
- 'çç',
97
- 'é',
98
- 'YWJjZGU$$$',
99
- '@@@@',
100
- 'abcd!',
101
- 'ab=cd',
102
- // "YWFh" is "aaa" in base64
103
- 'YWFh' + 'é',
104
- 'YWFh' + 'éé',
105
- 'YWFh' + 'ééé',
106
- 'YWFh' + 'éééé',
107
- // Invalid padding
108
- 'YWFh' + '=',
109
- 'YWFh' + '==',
110
- 'YWFh' + '===',
111
- 'YWFh' + '====',
112
- 'YWFh' + '=====',
113
- 'YWFh' + '======',
114
- 'TWEé',
115
- 'TWE👍',
116
- // More invalid padding
117
- // 'TWE=', // 'Ma'
118
- 'TWE=' + '=',
119
- 'TWE=' + '==',
120
- // 'TQ==', // 'M'
121
- 'TQ==' + '=',
122
- 'TQ==' + '==',
123
- ] as const) {
124
- it(`throws on invalid base64 string "${invalidB64}"`, () => {
125
- expect(() => fromBase64(invalidB64)).toThrow()
126
- })
127
- }
128
- })
129
- })
130
- }
@@ -1,98 +0,0 @@
1
- import { fromString } from 'uint8arrays/from-string'
2
- import { NodeJSBuffer } from './lib/nodejs-buffer.js'
3
- import { Base64Alphabet } from './uint8array-base64.js'
4
-
5
- const Buffer = NodeJSBuffer
6
-
7
- declare global {
8
- interface Uint8ArrayConstructor {
9
- /**
10
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/fromBase64 Uint8Array.fromBase64()}
11
- */
12
- fromBase64?: (
13
- b64: string,
14
- options?: {
15
- /** @default 'base64' */
16
- alphabet?: 'base64' | 'base64url'
17
- lastChunkHandling?: 'loose' | 'strict' | 'stop-before-partial'
18
- },
19
- ) => Uint8Array
20
- }
21
- }
22
-
23
- export const fromBase64Native =
24
- typeof Uint8Array.fromBase64 === 'function'
25
- ? function fromBase64Native(
26
- b64: string,
27
- alphabet: Base64Alphabet = 'base64',
28
- ): Uint8Array {
29
- return Uint8Array.fromBase64!(b64, {
30
- alphabet,
31
- lastChunkHandling: 'loose',
32
- })
33
- }
34
- : /* v8 ignore next -- @preserve */ null
35
-
36
- export const fromBase64Node = Buffer
37
- ? function fromBase64Node(
38
- b64: string,
39
- alphabet: Base64Alphabet = 'base64',
40
- ): Uint8Array {
41
- const bytes = Buffer.from(b64, alphabet)
42
- verifyBase64ForBytes(b64, bytes)
43
- // Convert to Uint8Array because even though Buffer is a sub class of
44
- // Uint8Array, it serializes differently to Uint8Array (e.g. in JSON) and
45
- // results in unexpected behavior downstream (e.g. in tests)
46
- return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength)
47
- }
48
- : /* v8 ignore next -- @preserve */ null
49
-
50
- export function fromBase64Ponyfill(
51
- b64: string,
52
- alphabet: Base64Alphabet = 'base64',
53
- ): Uint8Array {
54
- const bytes = fromString(b64, b64.endsWith('=') ? `${alphabet}pad` : alphabet)
55
- verifyBase64ForBytes(b64, bytes)
56
- return bytes
57
- }
58
-
59
- // @NOTE NodeJS will silently stop decoding at the first invalid character,
60
- // while "uint8arrays/from-string" will not validate that the padding is
61
- // correct. The following function performs basic validation to ensure that the
62
- // input was a valid base64 string. The availability of the "bytes" allows
63
- // to perform checks with O[1] complexity.
64
- function verifyBase64ForBytes(b64: string, bytes: Uint8Array): void {
65
- const paddingCount = b64.endsWith('==') ? 2 : b64.endsWith('=') ? 1 : 0
66
- const trimmedLength = b64.length - paddingCount
67
- const expectedByteLength = Math.floor((trimmedLength * 3) / 4)
68
- if (bytes.length !== expectedByteLength) {
69
- throw new Error('Invalid base64 string')
70
- }
71
-
72
- const expectedB64Length = (bytes.length / 3) * 4
73
- const expectedPaddingCount =
74
- expectedB64Length % 4 === 0 ? 0 : 4 - (expectedB64Length % 4)
75
- const expectedFullB64Length = expectedB64Length + expectedPaddingCount
76
- if (b64.length > expectedFullB64Length) {
77
- throw new Error('Invalid base64 string')
78
- }
79
-
80
- // The previous might still allow false positive if only the last few
81
- // chars are invalid.
82
- for (
83
- let i = Math.ceil(expectedB64Length);
84
- i < b64.length - paddingCount;
85
- i++
86
- ) {
87
- const code = b64.charCodeAt(i)
88
- if (
89
- !(code >= 65 && code <= 90) && // A-Z
90
- !(code >= 97 && code <= 122) && // a-z
91
- !(code >= 48 && code <= 57) && // 0-9
92
- code !== 43 && // +
93
- code !== 47 // /
94
- ) {
95
- throw new Error('Invalid base64 string')
96
- }
97
- }
98
- }