@bsv/sdk 1.9.16 → 1.9.17

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.
@@ -6099,25 +6099,31 @@ toUTF8 = (arr: number[]): string => {
6099
6099
  }
6100
6100
  if (byte <= 127) {
6101
6101
  result += String.fromCharCode(byte);
6102
+ continue;
6102
6103
  }
6103
- else if (byte >= 192 && byte <= 223) {
6104
- const byte2 = arr[i + 1];
6105
- skip = 1;
6104
+ if (byte >= 192 && byte <= 223) {
6105
+ const avail = arr.length - (i + 1);
6106
+ const byte2 = avail >= 1 ? arr[i + 1] : 0;
6107
+ skip = Math.min(1, avail);
6106
6108
  const codePoint = ((byte & 31) << 6) | (byte2 & 63);
6107
6109
  result += String.fromCharCode(codePoint);
6110
+ continue;
6108
6111
  }
6109
- else if (byte >= 224 && byte <= 239) {
6110
- const byte2 = arr[i + 1];
6111
- const byte3 = arr[i + 2];
6112
- skip = 2;
6112
+ if (byte >= 224 && byte <= 239) {
6113
+ const avail = arr.length - (i + 1);
6114
+ const byte2 = avail >= 1 ? arr[i + 1] : 0;
6115
+ const byte3 = avail >= 2 ? arr[i + 2] : 0;
6116
+ skip = Math.min(2, avail);
6113
6117
  const codePoint = ((byte & 15) << 12) | ((byte2 & 63) << 6) | (byte3 & 63);
6114
6118
  result += String.fromCharCode(codePoint);
6119
+ continue;
6115
6120
  }
6116
- else if (byte >= 240 && byte <= 247) {
6117
- const byte2 = arr[i + 1];
6118
- const byte3 = arr[i + 2];
6119
- const byte4 = arr[i + 3];
6120
- skip = 3;
6121
+ if (byte >= 240 && byte <= 247) {
6122
+ const avail = arr.length - (i + 1);
6123
+ const byte2 = avail >= 1 ? arr[i + 1] : 0;
6124
+ const byte3 = avail >= 2 ? arr[i + 2] : 0;
6125
+ const byte4 = avail >= 3 ? arr[i + 3] : 0;
6126
+ skip = Math.min(3, avail);
6121
6127
  const codePoint = ((byte & 7) << 18) |
6122
6128
  ((byte2 & 63) << 12) |
6123
6129
  ((byte3 & 63) << 6) |
@@ -6125,6 +6131,7 @@ toUTF8 = (arr: number[]): string => {
6125
6131
  const surrogate1 = 55296 + ((codePoint - 65536) >> 10);
6126
6132
  const surrogate2 = 56320 + ((codePoint - 65536) & 1023);
6127
6133
  result += String.fromCharCode(surrogate1, surrogate2);
6134
+ continue;
6128
6135
  }
6129
6136
  }
6130
6137
  return result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.9.16",
3
+ "version": "1.9.17",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -4,6 +4,7 @@ import {
4
4
  zero2,
5
5
  toHex,
6
6
  encode,
7
+ toUTF8,
7
8
  fromBase58,
8
9
  toBase58,
9
10
  fromBase58Check,
@@ -209,6 +210,48 @@ describe('utils', () => {
209
210
  })
210
211
  })
211
212
 
213
+ describe('toUTF8 bounds checks', () => {
214
+ const guarded = (arr: number[]): number[] => {
215
+ const target = arr.slice()
216
+ const handler: ProxyHandler<number[]> = {
217
+ get (t, prop, receiver) {
218
+ if (prop === 'length' || typeof prop !== 'string') {
219
+ return Reflect.get(t, prop as any, receiver)
220
+ }
221
+ const idx = Number(prop)
222
+ if (Number.isInteger(idx)) {
223
+ if (idx < 0 || idx >= t.length) {
224
+ throw new Error(`out-of-bounds read at index ${idx} (length ${t.length})`)
225
+ }
226
+ }
227
+ return Reflect.get(t, prop as any, receiver)
228
+ }
229
+ }
230
+ return new Proxy(target, handler) as unknown as number[]
231
+ }
232
+
233
+ it('does not access out-of-bounds on truncated 2-byte sequence', () => {
234
+ const input = guarded([0xC3])
235
+ expect(() => toUTF8(input)).not.toThrow()
236
+ })
237
+
238
+ it('does not access out-of-bounds on truncated 3-byte sequences', () => {
239
+ const input1 = guarded([0xE2])
240
+ const input2 = guarded([0xE2, 0x82])
241
+ expect(() => toUTF8(input1)).not.toThrow()
242
+ expect(() => toUTF8(input2)).not.toThrow()
243
+ })
244
+
245
+ it('does not access out-of-bounds on truncated 4-byte sequences', () => {
246
+ const input1 = guarded([0xF0])
247
+ const input2 = guarded([0xF0, 0x9F])
248
+ const input3 = guarded([0xF0, 0x9F, 0x98])
249
+ expect(() => toUTF8(input1)).not.toThrow()
250
+ expect(() => toUTF8(input2)).not.toThrow()
251
+ expect(() => toUTF8(input3)).not.toThrow()
252
+ })
253
+ })
254
+
212
255
  describe('toArray base64', () => {
213
256
  it('decodes empty string to empty array', () => {
214
257
  expect(toArray('', 'base64')).toEqual([])
@@ -237,7 +237,6 @@ export const toUTF8 = (arr: number[]): string => {
237
237
 
238
238
  for (let i = 0; i < arr.length; i++) {
239
239
  const byte = arr[i]
240
-
241
240
  // this byte is part of a multi-byte sequence, skip it
242
241
  // added to avoid modifying i within the loop which is considered unsafe.
243
242
  if (skip > 0) {
@@ -248,26 +247,41 @@ export const toUTF8 = (arr: number[]): string => {
248
247
  // 1-byte sequence (0xxxxxxx)
249
248
  if (byte <= 0x7f) {
250
249
  result += String.fromCharCode(byte)
251
- } else if (byte >= 0xc0 && byte <= 0xdf) {
252
- // 2-byte sequence (110xxxxx 10xxxxxx)
253
- const byte2 = arr[i + 1]
254
- skip = 1
250
+ continue
251
+ }
252
+
253
+ // 2-byte sequence (110xxxxx 10xxxxxx)
254
+ if (byte >= 0xc0 && byte <= 0xdf) {
255
+ const avail = arr.length - (i + 1)
256
+ const byte2 = avail >= 1 ? arr[i + 1] : 0
257
+ skip = Math.min(1, avail)
258
+
255
259
  const codePoint = ((byte & 0x1f) << 6) | (byte2 & 0x3f)
256
260
  result += String.fromCharCode(codePoint)
257
- } else if (byte >= 0xe0 && byte <= 0xef) {
258
- // 3-byte sequence (1110xxxx 10xxxxxx 10xxxxxx)
259
- const byte2 = arr[i + 1]
260
- const byte3 = arr[i + 2]
261
- skip = 2
261
+ continue
262
+ }
263
+
264
+ // 3-byte sequence (1110xxxx 10xxxxxx 10xxxxxx)
265
+ if (byte >= 0xe0 && byte <= 0xef) {
266
+ const avail = arr.length - (i + 1)
267
+ const byte2 = avail >= 1 ? arr[i + 1] : 0
268
+ const byte3 = avail >= 2 ? arr[i + 2] : 0
269
+ skip = Math.min(2, avail)
270
+
262
271
  const codePoint =
263
272
  ((byte & 0x0f) << 12) | ((byte2 & 0x3f) << 6) | (byte3 & 0x3f)
264
273
  result += String.fromCharCode(codePoint)
265
- } else if (byte >= 0xf0 && byte <= 0xf7) {
266
- // 4-byte sequence (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)
267
- const byte2 = arr[i + 1]
268
- const byte3 = arr[i + 2]
269
- const byte4 = arr[i + 3]
270
- skip = 3
274
+ continue
275
+ }
276
+
277
+ // 4-byte sequence (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)
278
+ if (byte >= 0xf0 && byte <= 0xf7) {
279
+ const avail = arr.length - (i + 1)
280
+ const byte2 = avail >= 1 ? arr[i + 1] : 0
281
+ const byte3 = avail >= 2 ? arr[i + 2] : 0
282
+ const byte4 = avail >= 3 ? arr[i + 3] : 0
283
+ skip = Math.min(3, avail)
284
+
271
285
  const codePoint =
272
286
  ((byte & 0x07) << 18) |
273
287
  ((byte2 & 0x3f) << 12) |
@@ -278,6 +292,7 @@ export const toUTF8 = (arr: number[]): string => {
278
292
  const surrogate1 = 0xd800 + ((codePoint - 0x10000) >> 10)
279
293
  const surrogate2 = 0xdc00 + ((codePoint - 0x10000) & 0x3ff)
280
294
  result += String.fromCharCode(surrogate1, surrogate2)
295
+ continue
281
296
  }
282
297
  }
283
298