forked from wangziqi/gongxue-base
fix: remove vulnerable excel runtime dependency
This commit is contained in:
@@ -12,9 +12,9 @@
|
||||
"dependencies": {
|
||||
"@supabase/storage-js": "^2.108.2",
|
||||
"ali-oss": "^6.23.0",
|
||||
"exceljs": "^4.4.0",
|
||||
"jose": "^6.2.3",
|
||||
"pg": "^8.16.3"
|
||||
"pg": "^8.16.3",
|
||||
"read-excel-file": "^9.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.4",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Buffer } from 'node:buffer';
|
||||
import ExcelJS from 'exceljs';
|
||||
import readXlsxFile, { readSheet } from 'read-excel-file/node';
|
||||
import { HttpError } from '../../core/http.js';
|
||||
|
||||
type JsonObject = Record<string, unknown>;
|
||||
@@ -678,18 +678,17 @@ function parseCsvBody(body: JsonObject, importType: SpreadsheetImportType): Pars
|
||||
};
|
||||
}
|
||||
|
||||
function worksheetRows(worksheet: ExcelJS.Worksheet, importType: SpreadsheetImportType, overrides: FieldMappingOverrides) {
|
||||
const matrix: string[][] = [];
|
||||
worksheet.eachRow({ includeEmpty: false }, row => {
|
||||
const values: string[] = [];
|
||||
const max = Math.min(row.cellCount, MAX_SPREADSHEET_COLUMNS);
|
||||
for (let col = 1; col <= max; col += 1) {
|
||||
const value = primitiveCellValue(row.getCell(col).value);
|
||||
values.push(value === undefined || value === null ? '' : String(value));
|
||||
}
|
||||
if (values.some(value => value.trim())) matrix.push(values);
|
||||
});
|
||||
return rowsFromMatrix(matrix, importType, overrides);
|
||||
function worksheetRows(matrix: unknown[][], importType: SpreadsheetImportType, overrides: FieldMappingOverrides) {
|
||||
const rows = matrix.map(line => {
|
||||
const values = line
|
||||
.slice(0, MAX_SPREADSHEET_COLUMNS)
|
||||
.map(value => {
|
||||
const primitive = primitiveCellValue(value);
|
||||
return primitive === undefined || primitive === null ? '' : String(primitive);
|
||||
});
|
||||
return values;
|
||||
}).filter(values => values.some(value => value.trim()));
|
||||
return rowsFromMatrix(rows, importType, overrides);
|
||||
}
|
||||
|
||||
async function parseExcelBody(body: JsonObject, importType: SpreadsheetImportType): Promise<ParsedSpreadsheet> {
|
||||
@@ -697,19 +696,20 @@ async function parseExcelBody(body: JsonObject, importType: SpreadsheetImportTyp
|
||||
if (!buffer) {
|
||||
throw new HttpError(400, 'Excel import requires fileBase64', 'EXCEL_FILE_REQUIRED');
|
||||
}
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
|
||||
let workbookSheets: Array<{ sheet: string; data: unknown[][] }>;
|
||||
try {
|
||||
await workbook.xlsx.load(buffer as unknown as Parameters<typeof workbook.xlsx.load>[0]);
|
||||
workbookSheets = await readXlsxFile(buffer);
|
||||
} catch {
|
||||
throw new HttpError(400, 'Excel file could not be parsed as .xlsx', 'EXCEL_PARSE_FAILED');
|
||||
}
|
||||
|
||||
const sheetName = stringValue(body.sheetName);
|
||||
const sheetIndex = Number(body.sheetIndex ?? 1);
|
||||
let selected: ExcelJS.Worksheet | undefined;
|
||||
if (sheetName) selected = workbook.getWorksheet(sheetName);
|
||||
else if (Number.isFinite(sheetIndex) && sheetIndex > 0) selected = workbook.getWorksheet(Math.trunc(sheetIndex));
|
||||
selected = selected || workbook.worksheets.find(sheet => sheet.actualRowCount > 0);
|
||||
let selected = sheetName
|
||||
? workbookSheets.find(sheet => sheet.sheet === sheetName)
|
||||
: workbookSheets[Math.max(0, Math.trunc(Number.isFinite(sheetIndex) && sheetIndex > 0 ? sheetIndex : 1) - 1)];
|
||||
selected = selected || workbookSheets.find(sheet => sheet.data.some(row => row.some(value => value !== null && value !== undefined && String(value).trim())));
|
||||
if (!selected) {
|
||||
throw new HttpError(400, 'Excel file has no non-empty worksheet', 'EXCEL_SHEET_EMPTY');
|
||||
}
|
||||
@@ -717,22 +717,31 @@ async function parseExcelBody(body: JsonObject, importType: SpreadsheetImportTyp
|
||||
const sheets: Record<string, JsonObject[]> = {};
|
||||
let totalRows = 0;
|
||||
const overrides = fieldMappingOverrides(body, importType);
|
||||
for (const sheet of workbook.worksheets) {
|
||||
if (sheet.actualRowCount === 0) continue;
|
||||
const rows = worksheetRows(sheet, importType, overrides);
|
||||
sheets[normalizedKey(sheet.name)] = rows;
|
||||
for (const sheet of workbookSheets) {
|
||||
if (!sheet.data.some(row => row.some(value => value !== null && value !== undefined && String(value).trim()))) continue;
|
||||
const rows = worksheetRows(sheet.data, importType, overrides);
|
||||
sheets[normalizedKey(sheet.sheet)] = rows;
|
||||
totalRows += rows.length;
|
||||
}
|
||||
assertRowLimit(totalRows);
|
||||
|
||||
const selectedRows = sheets[normalizedKey(selected.name)] || [];
|
||||
if (!sheets[normalizedKey(selected.sheet)]) {
|
||||
try {
|
||||
const selectedData = await readSheet(buffer, selected.sheet);
|
||||
sheets[normalizedKey(selected.sheet)] = worksheetRows(selectedData, importType, overrides);
|
||||
} catch {
|
||||
throw new HttpError(400, 'Excel file could not be parsed as .xlsx', 'EXCEL_PARSE_FAILED');
|
||||
}
|
||||
}
|
||||
|
||||
const selectedRows = sheets[normalizedKey(selected.sheet)] || [];
|
||||
return {
|
||||
rows: selectedRows,
|
||||
sheets,
|
||||
metadata: {
|
||||
parser: 'exceljs',
|
||||
workbookSheetCount: workbook.worksheets.length,
|
||||
selectedSheet: selected.name,
|
||||
parser: 'read-excel-file',
|
||||
workbookSheetCount: workbookSheets.length,
|
||||
selectedSheet: selected.sheet,
|
||||
rowCount: selectedRows.length,
|
||||
totalParsedRows: totalRows,
|
||||
fieldMappingOverrides: overrides.size,
|
||||
|
||||
Reference in New Issue
Block a user