From 1ced9aafc00c10992bab8bd3f1b6b1397f05b7b9 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 19 Dec 2023 00:33:38 -0500 Subject: Make JSON.parse usage safer (#373) * Make JSON.parse usage safer * Fix any type * Add readResponseJson * Use readResponseJson * Additional updates * Rename files * Add types --- dev/bin/schema-validate.js | 5 +++-- dev/build-libs.js | 7 ++++++- dev/dictionary-validate.js | 8 +++++--- dev/json.js | 18 ++++++++++++++++++ dev/manifest-util.js | 5 +++-- dev/schema-validate.js | 6 ++++-- dev/util.js | 5 +++-- 7 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 dev/json.js (limited to 'dev') diff --git a/dev/bin/schema-validate.js b/dev/bin/schema-validate.js index 206f26ca..bbd5ad5f 100644 --- a/dev/bin/schema-validate.js +++ b/dev/bin/schema-validate.js @@ -18,6 +18,7 @@ import fs from 'fs'; import {performance} from 'perf_hooks'; +import {parseJson} from '../../ext/js/core/json.js'; import {createJsonSchema} from '../schema-validate.js'; /** */ @@ -39,14 +40,14 @@ function main() { } const schemaSource = fs.readFileSync(args[0], {encoding: 'utf8'}); - const schema = JSON.parse(schemaSource); + const schema = parseJson(schemaSource); for (const dataFileName of args.slice(1)) { const start = performance.now(); try { console.log(`Validating ${dataFileName}...`); const dataSource = fs.readFileSync(dataFileName, {encoding: 'utf8'}); - const data = JSON.parse(dataSource); + const data = parseJson(dataSource); createJsonSchema(mode, schema).validate(data); const end = performance.now(); console.log(`No issues detected (${((end - start) / 1000).toFixed(2)}s)`); diff --git a/dev/build-libs.js b/dev/build-libs.js index a992f20a..10720010 100644 --- a/dev/build-libs.js +++ b/dev/build-libs.js @@ -22,6 +22,7 @@ import esbuild from 'esbuild'; import fs from 'fs'; import path from 'path'; import {fileURLToPath} from 'url'; +import {parseJson} from './json.js'; const dirname = path.dirname(fileURLToPath(import.meta.url)); const extDir = path.join(dirname, '..', 'ext'); @@ -61,7 +62,11 @@ export async function buildLibs() { const schemaDir = path.join(extDir, 'data/schemas/'); const schemaFileNames = fs.readdirSync(schemaDir); - const schemas = schemaFileNames.map((schemaFileName) => JSON.parse(fs.readFileSync(path.join(schemaDir, schemaFileName), {encoding: 'utf8'}))); + const schemas = schemaFileNames.map((schemaFileName) => { + /** @type {import('ajv').AnySchema} */ + const result = parseJson(fs.readFileSync(path.join(schemaDir, schemaFileName), {encoding: 'utf8'})); + return result; + }); const ajv = new Ajv({ schemas, code: {source: true, esm: true}, diff --git a/dev/dictionary-validate.js b/dev/dictionary-validate.js index 7842c65e..efc2eb8c 100644 --- a/dev/dictionary-validate.js +++ b/dev/dictionary-validate.js @@ -21,6 +21,7 @@ import JSZip from 'jszip'; import path from 'path'; import {performance} from 'perf_hooks'; import {fileURLToPath} from 'url'; +import {parseJson} from './json.js'; import {createJsonSchema} from './schema-validate.js'; const dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -32,7 +33,7 @@ const dirname = path.dirname(fileURLToPath(import.meta.url)); function readSchema(relativeFileName) { const fileName = path.join(dirname, relativeFileName); const source = fs.readFileSync(fileName, {encoding: 'utf8'}); - return JSON.parse(source); + return parseJson(source); } /** @@ -57,7 +58,7 @@ async function validateDictionaryBanks(mode, zip, fileNameFormat, schema) { const file = zip.files[fileName]; if (!file) { break; } - const data = JSON.parse(await file.async('string')); + const data = parseJson(await file.async('string')); try { jsonSchema.validate(data); } catch (e) { @@ -83,7 +84,8 @@ export async function validateDictionary(mode, archive, schemas) { throw new Error('No dictionary index found in archive'); } - const index = JSON.parse(await indexFile.async('string')); + /** @type {import('dictionary-data').Index} */ + const index = parseJson(await indexFile.async('string')); const version = index.format || index.version; try { diff --git a/dev/json.js b/dev/json.js new file mode 100644 index 00000000..a76edfcd --- /dev/null +++ b/dev/json.js @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +export {parseJson} from '../ext/js/core/json.js'; diff --git a/dev/manifest-util.js b/dev/manifest-util.js index 638706d8..ac9b58db 100644 --- a/dev/manifest-util.js +++ b/dev/manifest-util.js @@ -20,6 +20,7 @@ import childProcess from 'child_process'; import fs from 'fs'; import {fileURLToPath} from 'node:url'; import path from 'path'; +import {parseJson} from './json.js'; const dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -29,14 +30,14 @@ const dirname = path.dirname(fileURLToPath(import.meta.url)); * @returns {T} */ function clone(value) { - return JSON.parse(JSON.stringify(value)); + return parseJson(JSON.stringify(value)); } export class ManifestUtil { constructor() { const fileName = path.join(dirname, 'data', 'manifest-variants.json'); - const {manifest, variants, defaultVariant} = /** @type {import('dev/manifest').ManifestConfig} */ (JSON.parse(fs.readFileSync(fileName, {encoding: 'utf8'}))); + const {manifest, variants, defaultVariant} = /** @type {import('dev/manifest').ManifestConfig} */ (parseJson(fs.readFileSync(fileName, {encoding: 'utf8'}))); /** @type {import('dev/manifest').Manifest} */ this._manifest = manifest; /** @type {import('dev/manifest').ManifestVariant[]} */ diff --git a/dev/schema-validate.js b/dev/schema-validate.js index 81953f49..57faf96c 100644 --- a/dev/schema-validate.js +++ b/dev/schema-validate.js @@ -20,6 +20,7 @@ import Ajv from 'ajv'; import {readFileSync} from 'fs'; import {JsonSchema} from '../ext/js/data/json-schema.js'; import {DataError} from './data-error.js'; +import {parseJson} from './json.js'; class JsonSchemaAjv { /** @@ -32,7 +33,8 @@ class JsonSchemaAjv { allowUnionTypes: true }); const metaSchemaPath = require.resolve('ajv/dist/refs/json-schema-draft-07.json'); - const metaSchema = JSON.parse(readFileSync(metaSchemaPath, {encoding: 'utf8'})); + /** @type {import('ajv').AnySchemaObject} */ + const metaSchema = parseJson(readFileSync(metaSchemaPath, {encoding: 'utf8'})); ajv.addMetaSchema(metaSchema); /** @type {import('ajv').ValidateFunction} */ this._validate = ajv.compile(/** @type {import('ajv').Schema} */ (schema)); @@ -46,7 +48,7 @@ class JsonSchemaAjv { if (this._validate(data)) { return; } const {errors} = this._validate; const error = new DataError('Schema validation failed'); - error.data = JSON.parse(JSON.stringify(errors)); + error.data = parseJson(JSON.stringify(errors)); throw error; } } diff --git a/dev/util.js b/dev/util.js index f45966c4..6a7fa8f5 100644 --- a/dev/util.js +++ b/dev/util.js @@ -19,6 +19,7 @@ import fs from 'fs'; import JSZip from 'jszip'; import path from 'path'; +import {parseJson} from './json.js'; /** * @param {string[]} args @@ -112,9 +113,9 @@ export function createDictionaryArchive(dictionaryDirectory, dictionaryName) { for (const fileName of fileNames) { if (/\.json$/.test(fileName)) { const content = fs.readFileSync(path.join(dictionaryDirectory, fileName), {encoding: 'utf8'}); - const json = JSON.parse(content); + const json = parseJson(content); if (fileName === 'index.json' && typeof dictionaryName === 'string') { - json.title = dictionaryName; + /** @type {import('dictionary-data').Index} */ (json).title = dictionaryName; } archive.file(fileName, JSON.stringify(json, null, 0)); -- cgit v1.2.3