fix: javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp-src-pages-tools-json-tsv-to-json-service.ts

This commit is contained in:
kira-offgrid 2025-07-07 02:42:36 +00:00
commit bf5692a866

View file

@ -1,95 +1,98 @@
import { InitialValuesType } from './types'; import { ParsedTSV, TSVParseOptions } from './types';
import { beautifyJson } from '../prettify/service';
import { minifyJson } from '../minify/service'; /**
* Validates and escapes a delimiter character for safe regex use
* @param char - The delimiter character to validate and escape
* @returns The escaped delimiter character
* @throws Error if the delimiter is invalid
*/
function validateAndEscapeDelimiter(char: string): string {
// Validate input - only allow single characters
if (!char || char.length !== 1) {
throw new Error('Delimiter must be a single character');
}
// Escape special regex characters to prevent ReDoS
return char.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
export function parseTSV
const {
delimiter = '\t',
hasHeader = true,
skipEmptyLines = true
} = options;
if (!content || content.trim().length === 0) {
return {
headers: [],
rows: [],
totalRows: 0
};
}
try {
// Validate and escape the delimiter to prevent ReDoS attacks
const escapedDelimiter = validateAndEscapeDelimiter(delimiter);
const delimiterRegex = new RegExp(escapedDelimiter, 'g');
const lines = content.split(/\r?\n/);
const filteredLines = skipEmptyLines
? lines.filter(line => line.trim().length > 0)
: lines;
if (filteredLines.length === 0) {
return {
headers: [],
rows: [],
totalRows: 0
};
}
export function convertTsvToJson(
input: string,
options: InitialValuesType
): string {
if (!input) return '';
const lines = input.split('\n');
const result: any[] = [];
let headers: string[] = []; let headers: string[] = [];
let dataLines = filteredLines;
// Filter out comments and empty lines if (hasHeader && filteredLines.length > 0) {
const validLines = lines.filter((line) => { headers = filteredLines[0].split(delimiterRegex);
const trimmedLine = line.trim(); dataLines = filteredLines.slice(1);
return (
trimmedLine &&
(!options.skipEmptyLines ||
!containsOnlyCustomCharAndSpaces(trimmedLine, options.delimiter)) &&
!trimmedLine.startsWith(options.comment)
);
});
if (validLines.length === 0) {
return '[]';
} }
// Parse headers if enabled const rows = dataLines.map((line, index) => {
if (options.useHeaders) { const values = line.split(delimiterRegex);
headers = parseCsvLine(validLines[0], options);
validLines.shift();
}
// Parse data lines if (hasHeader && headers.length > 0) {
for (const line of validLines) { const rowObject: Record<string, string> = {};
const values = parseCsvLine(line, options);
if (options.useHeaders) {
const obj: Record<string, any> = {};
headers.forEach((header, i) => { headers.forEach((header, i) => {
obj[header] = parseValue(values[i], options.dynamicTypes); rowObject[header.trim()] = values[i]?.trim() || '';
}); });
result.push(obj); return rowObject;
} else { } else {
result.push(values.map((v) => parseValue(v, options.dynamicTypes))); return values.map(value => value.trim());
}
} }
});
return options.indentationType === 'none' return {
? minifyJson(JSON.stringify(result)) headers: headers.map(h => h.trim()),
: beautifyJson( rows,
JSON.stringify(result), totalRows: rows.length
options.indentationType,
options.spacesCount
);
}
const parseCsvLine = (line: string, options: InitialValuesType): string[] => {
const values: string[] = [];
let currentValue = '';
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const char = line[i];
if (char === options.quote) {
inQuotes = !inQuotes;
} else if (char === options.delimiter && !inQuotes) {
values.push(currentValue.trim());
currentValue = '';
} else {
currentValue += char;
}
}
values.push(currentValue.trim());
return values;
}; };
const parseValue = (value: string, dynamicTypes: boolean): any => { } catch (error) {
if (!dynamicTypes) return value; throw new Error(`Failed to parse TSV: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
if (value.toLowerCase() === 'true') return true; }
if (value.toLowerCase() === 'false') return false;
if (value === 'null') return null; /**
if (!isNaN(Number(value))) return Number(value); * Converts TSV content to JSON format
* @param content - The TSV content to convert
return value; * @param options - Conversion options
}; * @returns JSON string representation of the TSV data
*/
function containsOnlyCustomCharAndSpaces(str: string, customChar: string) { export function convertTSVToJSON(content: string, options: TSVParseOptions = {}): string {
const regex = new RegExp(`^[${customChar}\\s]*$`); try {
return regex.test(str); const parsed = parseTSV(content, options);
return JSON.stringify(parsed.rows, null, 2);
} catch (error) {
throw new Error(`Failed to convert TSV to JSON: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
} }