From 6460989165dcebf79e4f28982ce25cacc17582ad Mon Sep 17 00:00:00 2001 From: Aditi Khare <106987683+aditi-khare-mongoDB@users.noreply.github.com> Date: Thu, 2 May 2024 12:06:28 -0400 Subject: [PATCH] fix(NODE-6124): utf8 validation is insufficiently strict (#680) --- .../require_vendor.mjs | 34 ++++++---- src/binary.ts | 4 +- src/parse_utf8.ts | 35 ++++++++++ src/parser/deserializer.ts | 47 +++---------- src/utils/byte_utils.ts | 2 +- src/utils/node_byte_utils.ts | 13 +++- src/utils/web_byte_utils.ts | 16 +---- src/validate_utf8.ts | 47 ------------- test/node/byte_utils.test.ts | 19 +++++- test/node/data/utf8_wpt_error_cases.ts | 67 +++++++++++++++++++ test/node/parser/deserializer.test.ts | 46 ++++++++++++- test/node/release.test.ts | 2 +- 12 files changed, 213 insertions(+), 119 deletions(-) create mode 100644 src/parse_utf8.ts delete mode 100644 src/validate_utf8.ts create mode 100644 test/node/data/utf8_wpt_error_cases.ts diff --git a/etc/rollup/rollup-plugin-require-vendor/require_vendor.mjs b/etc/rollup/rollup-plugin-require-vendor/require_vendor.mjs index 7d4fa4e91..4819023dd 100644 --- a/etc/rollup/rollup-plugin-require-vendor/require_vendor.mjs +++ b/etc/rollup/rollup-plugin-require-vendor/require_vendor.mjs @@ -1,9 +1,12 @@ import MagicString from 'magic-string'; -const REQUIRE_POLYFILLS = - `const { TextEncoder, TextDecoder } = require('../vendor/text-encoding'); +const REQUIRE_WEB_UTILS_POLYFILLS = + `const { TextEncoder } = require('../vendor/text-encoding'); const { encode: btoa, decode: atob } = require('../vendor/base64');\n` +const REQUIRE_PARSE_UTF8_POLYFILLS = + `const { TextDecoder } = require('../vendor/text-encoding');\n`; + export class RequireVendor { /** * Take the compiled source code input; types are expected to already have been removed. @@ -14,17 +17,24 @@ export class RequireVendor { * @returns {{ code: string; map: import('magic-string').SourceMap }} */ transform(code, id) { - if (!id.includes('web_byte_utils')) { - return; - } + if (id.includes('parse_utf8')) { + // MagicString lets us edit the source code and still generate an accurate source map + const magicString = new MagicString(code); + magicString.prepend(REQUIRE_PARSE_UTF8_POLYFILLS); - // MagicString lets us edit the source code and still generate an accurate source map - const magicString = new MagicString(code); - magicString.prepend(REQUIRE_POLYFILLS); + return { + code: magicString.toString(), + map: magicString.generateMap({ hires: true }) + }; + } else if (id.includes('web_byte_utils')) { + // MagicString lets us edit the source code and still generate an accurate source map + const magicString = new MagicString(code); + magicString.prepend(REQUIRE_WEB_UTILS_POLYFILLS); - return { - code: magicString.toString(), - map: magicString.generateMap({ hires: true }) - }; + return { + code: magicString.toString(), + map: magicString.generateMap({ hires: true }) + }; + } } } diff --git a/src/binary.ts b/src/binary.ts index 25d5aa75b..15f7d6de2 100644 --- a/src/binary.ts +++ b/src/binary.ts @@ -224,8 +224,8 @@ export class Binary extends BSONValue { if (encoding === 'hex') return ByteUtils.toHex(this.buffer.subarray(0, this.position)); if (encoding === 'base64') return ByteUtils.toBase64(this.buffer.subarray(0, this.position)); if (encoding === 'utf8' || encoding === 'utf-8') - return ByteUtils.toUTF8(this.buffer, 0, this.position); - return ByteUtils.toUTF8(this.buffer, 0, this.position); + return ByteUtils.toUTF8(this.buffer, 0, this.position, false); + return ByteUtils.toUTF8(this.buffer, 0, this.position, false); } /** @internal */ diff --git a/src/parse_utf8.ts b/src/parse_utf8.ts new file mode 100644 index 000000000..a61c1202c --- /dev/null +++ b/src/parse_utf8.ts @@ -0,0 +1,35 @@ +import { BSONError } from './error'; + +type TextDecoder = { + readonly encoding: string; + readonly fatal: boolean; + readonly ignoreBOM: boolean; + decode(input?: Uint8Array): string; +}; +type TextDecoderConstructor = { + new (label: 'utf8', options: { fatal: boolean; ignoreBOM?: boolean }): TextDecoder; +}; + +// parse utf8 globals +declare const TextDecoder: TextDecoderConstructor; +let TextDecoderFatal: TextDecoder; +let TextDecoderNonFatal: TextDecoder; + +/** + * Determines if the passed in bytes are valid utf8 + * @param bytes - An array of 8-bit bytes. Must be indexable and have length property + * @param start - The index to start validating + * @param end - The index to end validating + */ +export function parseUtf8(buffer: Uint8Array, start: number, end: number, fatal: boolean): string { + if (fatal) { + TextDecoderFatal ??= new TextDecoder('utf8', { fatal: true }); + try { + return TextDecoderFatal.decode(buffer.subarray(start, end)); + } catch (cause) { + throw new BSONError('Invalid UTF-8 string in BSON document'); + } + } + TextDecoderNonFatal ??= new TextDecoder('utf8', { fatal: false }); + return TextDecoderNonFatal.decode(buffer.subarray(start, end)); +} diff --git a/src/parser/deserializer.ts b/src/parser/deserializer.ts index abb61046e..fc419d68c 100644 --- a/src/parser/deserializer.ts +++ b/src/parser/deserializer.ts @@ -15,7 +15,6 @@ import { BSONRegExp } from '../regexp'; import { BSONSymbol } from '../symbol'; import { Timestamp } from '../timestamp'; import { BSONDataView, ByteUtils } from '../utils/byte_utils'; -import { validateUtf8 } from '../validate_utf8'; /** @public */ export interface DeserializeOptions { @@ -236,7 +235,7 @@ function deserializeObject( if (i >= buffer.byteLength) throw new BSONError('Bad BSON Document: illegal CString'); // Represents the key - const name = isArray ? arrayIndex++ : ByteUtils.toUTF8(buffer, index, i); + const name = isArray ? arrayIndex++ : ByteUtils.toUTF8(buffer, index, i, false); // shouldValidateKey is true if the key should be validated, false otherwise let shouldValidateKey = true; @@ -266,7 +265,7 @@ function deserializeObject( ) { throw new BSONError('bad string length in bson'); } - value = getValidatedString(buffer, index, index + stringSize - 1, shouldValidateKey); + value = ByteUtils.toUTF8(buffer, index, index + stringSize - 1, shouldValidateKey); index = index + stringSize; } else if (elementType === constants.BSON_DATA_OID) { const oid = ByteUtils.allocate(12); @@ -476,7 +475,7 @@ function deserializeObject( // If are at the end of the buffer there is a problem with the document if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString'); // Return the C string - const source = ByteUtils.toUTF8(buffer, index, i); + const source = ByteUtils.toUTF8(buffer, index, i, false); // Create the regexp index = i + 1; @@ -489,7 +488,7 @@ function deserializeObject( // If are at the end of the buffer there is a problem with the document if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString'); // Return the C string - const regExpOptions = ByteUtils.toUTF8(buffer, index, i); + const regExpOptions = ByteUtils.toUTF8(buffer, index, i, false); index = i + 1; // For each option add the corresponding one for javascript @@ -521,7 +520,7 @@ function deserializeObject( // If are at the end of the buffer there is a problem with the document if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString'); // Return the C string - const source = ByteUtils.toUTF8(buffer, index, i); + const source = ByteUtils.toUTF8(buffer, index, i, false); index = i + 1; // Get the start search index @@ -533,7 +532,7 @@ function deserializeObject( // If are at the end of the buffer there is a problem with the document if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString'); // Return the C string - const regExpOptions = ByteUtils.toUTF8(buffer, index, i); + const regExpOptions = ByteUtils.toUTF8(buffer, index, i, false); index = i + 1; // Set the object @@ -551,7 +550,7 @@ function deserializeObject( ) { throw new BSONError('bad string length in bson'); } - const symbol = getValidatedString(buffer, index, index + stringSize - 1, shouldValidateKey); + const symbol = ByteUtils.toUTF8(buffer, index, index + stringSize - 1, shouldValidateKey); value = promoteValues ? symbol : new BSONSymbol(symbol); index = index + stringSize; } else if (elementType === constants.BSON_DATA_TIMESTAMP) { @@ -587,7 +586,7 @@ function deserializeObject( ) { throw new BSONError('bad string length in bson'); } - const functionString = getValidatedString( + const functionString = ByteUtils.toUTF8( buffer, index, index + stringSize - 1, @@ -626,7 +625,7 @@ function deserializeObject( } // Javascript function - const functionString = getValidatedString( + const functionString = ByteUtils.toUTF8( buffer, index, index + stringSize - 1, @@ -673,12 +672,7 @@ function deserializeObject( ) throw new BSONError('bad string length in bson'); // Namespace - if (validation != null && validation.utf8) { - if (!validateUtf8(buffer, index, index + stringSize - 1)) { - throw new BSONError('Invalid UTF-8 string in BSON document'); - } - } - const namespace = ByteUtils.toUTF8(buffer, index, index + stringSize - 1); + const namespace = ByteUtils.toUTF8(buffer, index, index + stringSize - 1, shouldValidateKey); // Update parse index position index = index + stringSize; @@ -728,24 +722,3 @@ function deserializeObject( return object; } - -function getValidatedString( - buffer: Uint8Array, - start: number, - end: number, - shouldValidateUtf8: boolean -) { - const value = ByteUtils.toUTF8(buffer, start, end); - // if utf8 validation is on, do the check - if (shouldValidateUtf8) { - for (let i = 0; i < value.length; i++) { - if (value.charCodeAt(i) === 0xfffd) { - if (!validateUtf8(buffer, start, end)) { - throw new BSONError('Invalid UTF-8 string in BSON document'); - } - break; - } - } - } - return value; -} diff --git a/src/utils/byte_utils.ts b/src/utils/byte_utils.ts index 41ec2c6a1..95cadddb3 100644 --- a/src/utils/byte_utils.ts +++ b/src/utils/byte_utils.ts @@ -26,7 +26,7 @@ export type ByteUtils = { /** Create a Uint8Array containing utf8 code units from a string */ fromUTF8: (text: string) => Uint8Array; /** Create a string from utf8 code units */ - toUTF8: (buffer: Uint8Array, start: number, end: number) => string; + toUTF8: (buffer: Uint8Array, start: number, end: number, fatal: boolean) => string; /** Get the utf8 code unit count from a string if it were to be transformed to utf8 */ utf8ByteLength: (input: string) => number; /** Encode UTF8 bytes generated from `source` string into `destination` at byteOffset. Returns the number of bytes encoded. */ diff --git a/src/utils/node_byte_utils.ts b/src/utils/node_byte_utils.ts index 214b1e39e..4c42c4db3 100644 --- a/src/utils/node_byte_utils.ts +++ b/src/utils/node_byte_utils.ts @@ -1,4 +1,5 @@ import { BSONError } from '../error'; +import { parseUtf8 } from '../parse_utf8'; type NodeJsEncoding = 'base64' | 'hex' | 'utf8' | 'binary'; type NodeJsBuffer = ArrayBufferView & @@ -125,8 +126,16 @@ export const nodeJsByteUtils = { return Buffer.from(text, 'utf8'); }, - toUTF8(buffer: Uint8Array, start: number, end: number): string { - return nodeJsByteUtils.toLocalBufferType(buffer).toString('utf8', start, end); + toUTF8(buffer: Uint8Array, start: number, end: number, fatal: boolean): string { + const value = nodeJsByteUtils.toLocalBufferType(buffer).toString('utf8', start, end); + if (fatal) { + for (let i = 0; i < value.length; i++) { + if (value.charCodeAt(i) === 0xfffd) { + parseUtf8(buffer, start, end, fatal); + } + } + } + return value; }, utf8ByteLength(input: string): number { diff --git a/src/utils/web_byte_utils.ts b/src/utils/web_byte_utils.ts index cf93e43a1..e1227fcb4 100644 --- a/src/utils/web_byte_utils.ts +++ b/src/utils/web_byte_utils.ts @@ -1,14 +1,5 @@ import { BSONError } from '../error'; - -type TextDecoder = { - readonly encoding: string; - readonly fatal: boolean; - readonly ignoreBOM: boolean; - decode(input?: Uint8Array): string; -}; -type TextDecoderConstructor = { - new (label: 'utf8', options: { fatal: boolean; ignoreBOM?: boolean }): TextDecoder; -}; +import { parseUtf8 } from '../parse_utf8'; type TextEncoder = { readonly encoding: string; @@ -19,7 +10,6 @@ type TextEncoderConstructor = { }; // Web global -declare const TextDecoder: TextDecoderConstructor; declare const TextEncoder: TextEncoderConstructor; declare const atob: (base64: string) => string; declare const btoa: (binary: string) => string; @@ -172,8 +162,8 @@ export const webByteUtils = { return new TextEncoder().encode(text); }, - toUTF8(uint8array: Uint8Array, start: number, end: number): string { - return new TextDecoder('utf8', { fatal: false }).decode(uint8array.slice(start, end)); + toUTF8(uint8array: Uint8Array, start: number, end: number, fatal: boolean): string { + return parseUtf8(uint8array, start, end, fatal); }, utf8ByteLength(input: string): number { diff --git a/src/validate_utf8.ts b/src/validate_utf8.ts deleted file mode 100644 index e1da934c6..000000000 --- a/src/validate_utf8.ts +++ /dev/null @@ -1,47 +0,0 @@ -const FIRST_BIT = 0x80; -const FIRST_TWO_BITS = 0xc0; -const FIRST_THREE_BITS = 0xe0; -const FIRST_FOUR_BITS = 0xf0; -const FIRST_FIVE_BITS = 0xf8; - -const TWO_BIT_CHAR = 0xc0; -const THREE_BIT_CHAR = 0xe0; -const FOUR_BIT_CHAR = 0xf0; -const CONTINUING_CHAR = 0x80; - -/** - * Determines if the passed in bytes are valid utf8 - * @param bytes - An array of 8-bit bytes. Must be indexable and have length property - * @param start - The index to start validating - * @param end - The index to end validating - */ -export function validateUtf8( - bytes: { [index: number]: number }, - start: number, - end: number -): boolean { - let continuation = 0; - - for (let i = start; i < end; i += 1) { - const byte = bytes[i]; - - if (continuation) { - if ((byte & FIRST_TWO_BITS) !== CONTINUING_CHAR) { - return false; - } - continuation -= 1; - } else if (byte & FIRST_BIT) { - if ((byte & FIRST_THREE_BITS) === TWO_BIT_CHAR) { - continuation = 1; - } else if ((byte & FIRST_FOUR_BITS) === THREE_BIT_CHAR) { - continuation = 2; - } else if ((byte & FIRST_FIVE_BITS) === FOUR_BIT_CHAR) { - continuation = 3; - } else { - return false; - } - } - } - - return !continuation; -} diff --git a/test/node/byte_utils.test.ts b/test/node/byte_utils.test.ts index 81fba0c40..447cffca7 100644 --- a/test/node/byte_utils.test.ts +++ b/test/node/byte_utils.test.ts @@ -8,6 +8,7 @@ import { webByteUtils } from '../../src/utils/web_byte_utils'; import * as sinon from 'sinon'; import { loadCJSModuleBSON, loadReactNativeCJSModuleBSON, loadESModuleBSON } from '../load_bson'; import * as crypto from 'node:crypto'; +import { utf8WebPlatformSpecTests } from './data/utf8_wpt_error_cases'; type ByteUtilTest = { name: string; @@ -400,7 +401,7 @@ const fromUTF8Tests: ByteUtilTest<'fromUTF8'>[] = [ const toUTF8Tests: ByteUtilTest<'toUTF8'>[] = [ { name: 'should create utf8 string from buffer input', - inputs: [Buffer.from('abc\u{1f913}', 'utf8')], + inputs: [Buffer.from('abc\u{1f913}', 'utf8'), 0, 7, false], expectation({ output, error }) { expect(error).to.be.null; expect(output).to.deep.equal(Buffer.from('abc\u{1f913}', 'utf8').toString('utf8')); @@ -408,12 +409,24 @@ const toUTF8Tests: ByteUtilTest<'toUTF8'>[] = [ }, { name: 'should return empty string for empty buffer input', - inputs: [Buffer.alloc(0)], + inputs: [Buffer.alloc(0), 0, 0, false], expectation({ output, error }) { expect(error).to.be.null; expect(output).to.be.a('string').with.lengthOf(0); } - } + }, + ...utf8WebPlatformSpecTests.map(t => ({ + name: t.name, + inputs: [Uint8Array.from(t.input), 0, t.input.length, true] as [ + buffer: Uint8Array, + start: number, + end: number, + fatal: boolean + ], + expectation({ error }) { + expect(error).to.match(/Invalid UTF-8 string in BSON document/i); + } + })) ]; const utf8ByteLengthTests: ByteUtilTest<'utf8ByteLength'>[] = [ { diff --git a/test/node/data/utf8_wpt_error_cases.ts b/test/node/data/utf8_wpt_error_cases.ts new file mode 100644 index 000000000..6d3a98135 --- /dev/null +++ b/test/node/data/utf8_wpt_error_cases.ts @@ -0,0 +1,67 @@ +// extra error cases copied from wpt/encoding/textdecoder-fatal.any.js +// commit sha: 7c9f867 +// link: https://github.com/web-platform-tests/wpt/commit/7c9f8674d9809731e8919073d957d6233f6e0544 + +export const utf8WebPlatformSpecTests = [ + { encoding: 'utf-8', input: [0xff], name: 'invalid code' }, + { encoding: 'utf-8', input: [0xc0], name: 'ends early' }, + { encoding: 'utf-8', input: [0xe0], name: 'ends early 2' }, + { encoding: 'utf-8', input: [0xc0, 0x00], name: 'invalid trail' }, + { encoding: 'utf-8', input: [0xc0, 0xc0], name: 'invalid trail 2' }, + { encoding: 'utf-8', input: [0xe0, 0x00], name: 'invalid trail 3' }, + { encoding: 'utf-8', input: [0xe0, 0xc0], name: 'invalid trail 4' }, + { encoding: 'utf-8', input: [0xe0, 0x80, 0x00], name: 'invalid trail 5' }, + { encoding: 'utf-8', input: [0xe0, 0x80, 0xc0], name: 'invalid trail 6' }, + { encoding: 'utf-8', input: [0xfc, 0x80, 0x80, 0x80, 0x80, 0x80], name: '> 0x10ffff' }, + { encoding: 'utf-8', input: [0xfe, 0x80, 0x80, 0x80, 0x80, 0x80], name: 'obsolete lead byte' }, + + // Overlong encodings + { encoding: 'utf-8', input: [0xc0, 0x80], name: 'overlong U+0000 - 2 bytes' }, + { encoding: 'utf-8', input: [0xe0, 0x80, 0x80], name: 'overlong U+0000 - 3 bytes' }, + { encoding: 'utf-8', input: [0xf0, 0x80, 0x80, 0x80], name: 'overlong U+0000 - 4 bytes' }, + { encoding: 'utf-8', input: [0xf8, 0x80, 0x80, 0x80, 0x80], name: 'overlong U+0000 - 5 bytes' }, + { + encoding: 'utf-8', + input: [0xfc, 0x80, 0x80, 0x80, 0x80, 0x80], + name: 'overlong U+0000 - 6 bytes' + }, + + { encoding: 'utf-8', input: [0xc1, 0xbf], name: 'overlong U+007f - 2 bytes' }, + { encoding: 'utf-8', input: [0xe0, 0x81, 0xbf], name: 'overlong U+007f - 3 bytes' }, + { encoding: 'utf-8', input: [0xf0, 0x80, 0x81, 0xbf], name: 'overlong U+007f - 4 bytes' }, + { encoding: 'utf-8', input: [0xf8, 0x80, 0x80, 0x81, 0xbf], name: 'overlong U+007f - 5 bytes' }, + { + encoding: 'utf-8', + input: [0xfc, 0x80, 0x80, 0x80, 0x81, 0xbf], + name: 'overlong U+007f - 6 bytes' + }, + + { encoding: 'utf-8', input: [0xe0, 0x9f, 0xbf], name: 'overlong U+07ff - 3 bytes' }, + { encoding: 'utf-8', input: [0xf0, 0x80, 0x9f, 0xbf], name: 'overlong U+07ff - 4 bytes' }, + { encoding: 'utf-8', input: [0xf8, 0x80, 0x80, 0x9f, 0xbf], name: 'overlong U+07ff - 5 bytes' }, + { + encoding: 'utf-8', + input: [0xfc, 0x80, 0x80, 0x80, 0x9f, 0xbf], + name: 'overlong U+07ff - 6 bytes' + }, + + { encoding: 'utf-8', input: [0xf0, 0x8f, 0xbf, 0xbf], name: 'overlong U+ffff - 4 bytes' }, + { encoding: 'utf-8', input: [0xf8, 0x80, 0x8f, 0xbf, 0xbf], name: 'overlong U+ffff - 5 bytes' }, + { + encoding: 'utf-8', + input: [0xfc, 0x80, 0x80, 0x8f, 0xbf, 0xbf], + name: 'overlong U+ffff - 6 bytes' + }, + + { encoding: 'utf-8', input: [0xf8, 0x84, 0x8f, 0xbf, 0xbf], name: 'overlong U+10ffff - 5 bytes' }, + { + encoding: 'utf-8', + input: [0xfc, 0x80, 0x84, 0x8f, 0xbf, 0xbf], + name: 'overlong U+10ffff - 6 bytes' + }, + + // UTf-16 surrogates encoded as code points in UTf-8 + { encoding: 'utf-8', input: [0xed, 0xa0, 0x80], name: 'lead surrogate' }, + { encoding: 'utf-8', input: [0xed, 0xb0, 0x80], name: 'trail surrogate' }, + { encoding: 'utf-8', input: [0xed, 0xa0, 0x80, 0xed, 0xb0, 0x80], name: 'surrogate pair' } +]; diff --git a/test/node/parser/deserializer.test.ts b/test/node/parser/deserializer.test.ts index 005ccefa4..30c684be5 100644 --- a/test/node/parser/deserializer.test.ts +++ b/test/node/parser/deserializer.test.ts @@ -1,6 +1,7 @@ import * as BSON from '../../register-bson'; import { expect } from 'chai'; -import { bufferFromHexArray } from '../tools/utils'; +import { bufferFromHexArray, int32LEToHex } from '../tools/utils'; +import { utf8WebPlatformSpecTests } from '../data/utf8_wpt_error_cases'; describe('deserializer()', () => { describe('when the fieldsAsRaw options is present and has a value that corresponds to a key in the object', () => { @@ -58,4 +59,47 @@ describe('deserializer()', () => { expect(resultCodeWithScope).to.have.deep.nested.property('a.scope', { b: true }); }); }); + + describe('utf8 validation', () => { + for (const test of utf8WebPlatformSpecTests) { + const inputStringSize = int32LEToHex(test.input.length + 1); // int32 size of string + const inputHexString = Buffer.from(test.input).toString('hex'); + const buffer = bufferFromHexArray([ + '02', // string + '6100', // 'a' key with null terminator + inputStringSize, + inputHexString, + '00' + ]); + context(`when utf8 validation is on and input is ${test.name}`, () => { + it(`throws error containing 'Invalid UTF-8'`, () => { + // global case + expect(() => BSON.deserialize(buffer, { validation: { utf8: true } })).to.throw( + BSON.BSONError, + /Invalid UTF-8 string in BSON document/i + ); + + // specific case + expect(() => BSON.deserialize(buffer, { validation: { utf8: { a: true } } })).to.throw( + BSON.BSONError, + /Invalid UTF-8 string in BSON document/i + ); + }); + }); + + context(`when utf8 validation is off and input is ${test.name}`, () => { + it('returns a string containing at least 1 replacement character', () => { + // global case + expect(BSON.deserialize(buffer, { validation: { utf8: false } })) + .to.have.property('a') + .that.includes('\uFFFD'); + + // specific case + expect(BSON.deserialize(buffer, { validation: { utf8: { a: false } } })) + .to.have.property('a') + .that.includes('\uFFFD'); + }); + }); + } + }); }); diff --git a/test/node/release.test.ts b/test/node/release.test.ts index b34f05179..19dbd0def 100644 --- a/test/node/release.test.ts +++ b/test/node/release.test.ts @@ -46,7 +46,7 @@ const REQUIRED_FILES = [ 'src/utils/byte_utils.ts', 'src/utils/node_byte_utils.ts', 'src/utils/web_byte_utils.ts', - 'src/validate_utf8.ts', + 'src/parse_utf8.ts', 'vendor/base64/base64.js', 'vendor/base64/package.json', 'vendor/base64/LICENSE-MIT.txt',