@mitre/hdf-utilities 3.0.0 → 3.1.0-rc.1
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.
- package/LICENSE.md +55 -0
- package/dist/csv/index.d.ts +27 -29
- package/dist/csv/index.d.ts.map +1 -1
- package/dist/csv/index.js +179 -218
- package/dist/csv/index.js.map +1 -1
- package/dist/hash/index.d.ts +15 -12
- package/dist/hash/index.d.ts.map +1 -1
- package/dist/hash/index.js +90 -98
- package/dist/hash/index.js.map +1 -1
- package/dist/index.d.ts +36 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +55 -15
- package/dist/index.js.map +1 -1
- package/dist/json/index.d.ts +13 -9
- package/dist/json/index.d.ts.map +1 -1
- package/dist/json/index.js +45 -54
- package/dist/json/index.js.map +1 -1
- package/dist/object/index.d.ts +6 -3
- package/dist/object/index.d.ts.map +1 -1
- package/dist/object/index.js +54 -60
- package/dist/object/index.js.map +1 -1
- package/dist/string/index.d.ts +5 -2
- package/dist/string/index.d.ts.map +1 -1
- package/dist/string/index.js +55 -56
- package/dist/string/index.js.map +1 -1
- package/dist/xml/index.d.ts +13 -13
- package/dist/xml/index.d.ts.map +1 -1
- package/dist/xml/index.js +136 -166
- package/dist/xml/index.js.map +1 -1
- package/package.json +30 -30
package/LICENSE.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# License
|
|
2
|
+
|
|
3
|
+
Copyright © 2025 The MITRE Corporation.
|
|
4
|
+
|
|
5
|
+
Approved for Public Release; Distribution Unlimited. Case Number 18-3678.
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
8
|
+
not use this file except in compliance with the License. You may obtain a
|
|
9
|
+
copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
15
|
+
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
16
|
+
License for the specific language governing permissions and limitations
|
|
17
|
+
under the License.
|
|
18
|
+
|
|
19
|
+
## Redistribution Terms
|
|
20
|
+
|
|
21
|
+
Redistribution and use in source and binary forms, with or without
|
|
22
|
+
modification, are permitted provided that the following conditions are
|
|
23
|
+
met:
|
|
24
|
+
|
|
25
|
+
- Redistributions of source code must retain the above copyright/digital
|
|
26
|
+
rights legend, this list of conditions and the following Notice.
|
|
27
|
+
- Redistributions in binary form must reproduce the above
|
|
28
|
+
copyright/digital rights legend, this list of conditions and the
|
|
29
|
+
following Notice in the documentation and/or other materials provided
|
|
30
|
+
with the distribution.
|
|
31
|
+
- Neither the name of The MITRE Corporation nor the names of its contributors
|
|
32
|
+
may be used to endorse or promote products derived from this software
|
|
33
|
+
without specific prior written permission.
|
|
34
|
+
|
|
35
|
+
## Notice
|
|
36
|
+
|
|
37
|
+
The MITRE Corporation grants permission to reproduce, distribute, modify, and
|
|
38
|
+
otherwise use this software to the extent permitted by the licensed terms
|
|
39
|
+
provided in the LICENSE file included with this project.
|
|
40
|
+
|
|
41
|
+
This software was produced by The MITRE Corporation for the U.S. Government
|
|
42
|
+
under contract. As such the U.S. Government has certain use and data
|
|
43
|
+
rights in this software. No use other than those granted to the U.S.
|
|
44
|
+
Government, or to those acting on behalf of the U.S. Government, under
|
|
45
|
+
these contract arrangements is authorized without the express written
|
|
46
|
+
permission of The MITRE Corporation.
|
|
47
|
+
|
|
48
|
+
Some files in this codebase were generated by generative AI, under the
|
|
49
|
+
direction and review of The MITRE Corporation employees, for the purpose of
|
|
50
|
+
development efficiency. All AI-generated code functionality was validated
|
|
51
|
+
by standard quality and assurance testing.
|
|
52
|
+
|
|
53
|
+
For further information, please contact The MITRE Corporation,
|
|
54
|
+
Contracts Management Office, 7515 Colshire Drive, McLean, VA 22102-7539,
|
|
55
|
+
(703) 983-6000.
|
package/dist/csv/index.d.ts
CHANGED
|
@@ -1,29 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* SECURITY WARNING: CSV files opened in Excel/LibreOffice may execute formulas.
|
|
5
|
-
* Use sanitization options when exporting data that may contain user-controlled content.
|
|
6
|
-
*/
|
|
7
|
-
export { extractColumn as extractCsvColumn, findRows as findCsvRows, } from '../object/index.js';
|
|
1
|
+
import { extractColumn, findRows } from "../object/index.js";
|
|
2
|
+
|
|
3
|
+
//#region src/csv/index.d.ts
|
|
8
4
|
/**
|
|
9
5
|
* Options for parsing CSV
|
|
10
6
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
interface CsvParseOptions {
|
|
8
|
+
header?: boolean;
|
|
9
|
+
skipEmptyLines?: boolean;
|
|
10
|
+
transformHeader?: (header: string, index: number) => string;
|
|
11
|
+
dynamicTyping?: boolean;
|
|
12
|
+
delimiter?: string;
|
|
13
|
+
maxSize?: number;
|
|
18
14
|
}
|
|
19
15
|
/**
|
|
20
16
|
* Options for building CSV
|
|
21
17
|
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
interface CsvBuildOptions {
|
|
19
|
+
header?: boolean;
|
|
20
|
+
newline?: string;
|
|
21
|
+
skipEmptyLines?: boolean;
|
|
22
|
+
delimiter?: string;
|
|
27
23
|
}
|
|
28
24
|
/**
|
|
29
25
|
* Parse CSV string into array of objects with headers as keys
|
|
@@ -32,7 +28,7 @@ export interface CsvBuildOptions {
|
|
|
32
28
|
* @returns Array of objects representing rows
|
|
33
29
|
* @throws Error if parsing fails
|
|
34
30
|
*/
|
|
35
|
-
|
|
31
|
+
declare function parseCsv<T = Record<string, unknown>>(csv: string, options?: Partial<CsvParseOptions>): T[];
|
|
36
32
|
/**
|
|
37
33
|
* Parse CSV string into array of arrays without treating first row as headers
|
|
38
34
|
* @param csv - CSV string to parse
|
|
@@ -40,7 +36,7 @@ export declare function parseCsv<T = Record<string, unknown>>(csv: string, optio
|
|
|
40
36
|
* @returns Array of arrays representing rows
|
|
41
37
|
* @throws Error if parsing fails
|
|
42
38
|
*/
|
|
43
|
-
|
|
39
|
+
declare function parseCsvArray(csv: string, options?: Partial<CsvParseOptions>): string[][];
|
|
44
40
|
/**
|
|
45
41
|
* Build CSV string from array of objects
|
|
46
42
|
*
|
|
@@ -51,8 +47,8 @@ export declare function parseCsvArray(csv: string, options?: Partial<CsvParseOpt
|
|
|
51
47
|
* @param options - Optional build configuration and sanitize flag
|
|
52
48
|
* @returns CSV string
|
|
53
49
|
*/
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
declare function buildCsv<T = Record<string, unknown>>(data: T[], options?: Partial<CsvBuildOptions> & {
|
|
51
|
+
sanitize?: boolean;
|
|
56
52
|
}): string;
|
|
57
53
|
/**
|
|
58
54
|
* Build CSV string from array of arrays
|
|
@@ -64,15 +60,15 @@ export declare function buildCsv<T = Record<string, unknown>>(data: T[], options
|
|
|
64
60
|
* @param options - Optional build configuration and sanitize flag
|
|
65
61
|
* @returns CSV string
|
|
66
62
|
*/
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
declare function buildCsvArray(data: string[][], options?: Partial<CsvBuildOptions> & {
|
|
64
|
+
sanitize?: boolean;
|
|
69
65
|
}): string;
|
|
70
66
|
/**
|
|
71
67
|
* Validate CSV format
|
|
72
68
|
* @param csv - CSV string to validate
|
|
73
69
|
* @returns True if valid CSV, false otherwise
|
|
74
70
|
*/
|
|
75
|
-
|
|
71
|
+
declare function isValidCsv(csv: string): boolean;
|
|
76
72
|
/**
|
|
77
73
|
* Sanitize a single CSV value to prevent formula injection
|
|
78
74
|
*
|
|
@@ -82,19 +78,21 @@ export declare function isValidCsv(csv: string): boolean;
|
|
|
82
78
|
* @param value - Value to sanitize
|
|
83
79
|
* @returns Sanitized value
|
|
84
80
|
*/
|
|
85
|
-
|
|
81
|
+
declare function sanitizeCsvValue(value: unknown): string;
|
|
86
82
|
/**
|
|
87
83
|
* Sanitize all values in an array to prevent formula injection
|
|
88
84
|
*
|
|
89
85
|
* @param values - Array of values to sanitize
|
|
90
86
|
* @returns Array with sanitized values
|
|
91
87
|
*/
|
|
92
|
-
|
|
88
|
+
declare function sanitizeCsvArray(values: unknown[]): string[];
|
|
93
89
|
/**
|
|
94
90
|
* Sanitize all values in an object to prevent formula injection
|
|
95
91
|
*
|
|
96
92
|
* @param obj - Object with values to sanitize
|
|
97
93
|
* @returns New object with sanitized values
|
|
98
94
|
*/
|
|
99
|
-
|
|
95
|
+
declare function sanitizeCsvObject<T extends Record<string, unknown>>(obj: T): Record<string, string>;
|
|
96
|
+
//#endregion
|
|
97
|
+
export { CsvBuildOptions, CsvParseOptions, buildCsv, buildCsvArray, extractColumn as extractCsvColumn, findRows as findCsvRows, isValidCsv, parseCsv, parseCsvArray, sanitizeCsvArray, sanitizeCsvObject, sanitizeCsvValue };
|
|
100
98
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/csv/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/csv/index.ts"],"mappings":";;;;;;UAiBiB,eAAA;EACf,MAAA;EACA,cAAA;EACA,eAAA,IAAmB,MAAA,UAAgB,KAAA;EACnC,aAAA;EACA,SAAA;EACA,OAAA;AAAA;;;;UAMe,eAAA;EACf,MAAA;EACA,OAAA;EACA,cAAA;EACA,SAAA;AAAA;AAwEF;;;;;;;AAAA,iBAAgB,QAAA,KAAa,MAAA,kBAAA,CAC3B,GAAA,UACA,OAAA,GAAU,OAAA,CAAQ,eAAA,IACjB,CAAA;;;;;;;;iBAoDa,aAAA,CACd,GAAA,UACA,OAAA,GAAU,OAAA,CAAQ,eAAA;;;AAFpB;;;;;;;;iBAmCgB,QAAA,KAAa,MAAA,kBAAA,CAC3B,IAAA,EAAM,CAAA,IACN,OAAA,GAAU,OAAA,CAAQ,eAAA;EAAqB,QAAA;AAAA;;;;;;;;;;;iBA2CzB,aAAA,CACd,IAAA,cACA,OAAA,GAAU,OAAA,CAAQ,eAAA;EAAqB,QAAA;AAAA;;;;;;iBA2BzB,UAAA,CAAW,GAAA;;;;;;;;;;iBA0BX,gBAAA,CAAiB,KAAA;AA1BjC;;;;;AA0BA;AA1BA,iBAyCgB,gBAAA,CAAiB,MAAA;;;;AAAjC;;;iBAUgB,iBAAA,WAA4B,MAAA,kBAAA,CAC1C,GAAA,EAAK,CAAA,GACJ,MAAA"}
|
package/dist/csv/index.js
CHANGED
|
@@ -1,251 +1,212 @@
|
|
|
1
|
+
import { extractColumn, findRows } from "../object/index.js";
|
|
2
|
+
import { dsvFormat } from "d3-dsv";
|
|
3
|
+
//#region src/csv/index.ts
|
|
1
4
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { dsvFormat } from 'd3-dsv';
|
|
8
|
-
export { extractColumn as extractCsvColumn, findRows as findCsvRows, } from '../object/index.js';
|
|
5
|
+
* CSV parsing and generation utilities using d3-dsv
|
|
6
|
+
*
|
|
7
|
+
* SECURITY WARNING: CSV files opened in Excel/LibreOffice may execute formulas.
|
|
8
|
+
* Use sanitization options when exporting data that may contain user-controlled content.
|
|
9
|
+
*/
|
|
9
10
|
/**
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
* Default options for parsing CSV with headers
|
|
12
|
+
*/
|
|
12
13
|
const DEFAULT_PARSE_OPTIONS = {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
header: true,
|
|
15
|
+
skipEmptyLines: true,
|
|
16
|
+
transformHeader: void 0,
|
|
17
|
+
dynamicTyping: false
|
|
17
18
|
};
|
|
18
19
|
/**
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
* Default options for parsing CSV without headers
|
|
21
|
+
*/
|
|
21
22
|
const DEFAULT_PARSE_ARRAY_OPTIONS = {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
header: false,
|
|
24
|
+
skipEmptyLines: true,
|
|
25
|
+
dynamicTyping: false
|
|
25
26
|
};
|
|
26
27
|
/**
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
* Default options for building CSV
|
|
29
|
+
*/
|
|
29
30
|
const DEFAULT_BUILD_OPTIONS = {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
header: true,
|
|
32
|
+
newline: "\n",
|
|
33
|
+
skipEmptyLines: true
|
|
33
34
|
};
|
|
34
35
|
/**
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
* Validate CSV quoting — throws if unclosed quotes are found
|
|
37
|
+
*/
|
|
37
38
|
function validateQuoting(csv) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
inQuotes = !inQuotes;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
else if (csv[i] === '\n' && !inQuotes) {
|
|
50
|
-
row++;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
if (inQuotes) {
|
|
54
|
-
throw new Error(`CSV parsing failed: Row ${row}: Unclosed quote`);
|
|
55
|
-
}
|
|
39
|
+
let inQuotes = false;
|
|
40
|
+
let row = 0;
|
|
41
|
+
for (let i = 0; i < csv.length; i++) if (csv[i] === "\"") if (inQuotes && i + 1 < csv.length && csv[i + 1] === "\"") i++;
|
|
42
|
+
else inQuotes = !inQuotes;
|
|
43
|
+
else if (csv[i] === "\n" && !inQuotes) row++;
|
|
44
|
+
if (inQuotes) throw new Error(`CSV parsing failed: Row ${row}: Unclosed quote`);
|
|
56
45
|
}
|
|
57
46
|
/**
|
|
58
|
-
|
|
59
|
-
|
|
47
|
+
* Convert string value to number or boolean when appropriate
|
|
48
|
+
*/
|
|
60
49
|
function dynamicTypeConvert(value) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const num = Number(value);
|
|
68
|
-
if (!isNaN(num) && value.trim() !== '')
|
|
69
|
-
return num;
|
|
70
|
-
return value;
|
|
50
|
+
if (value === "") return value;
|
|
51
|
+
if (value === "true") return true;
|
|
52
|
+
if (value === "false") return false;
|
|
53
|
+
const num = Number(value);
|
|
54
|
+
if (!isNaN(num) && value.trim() !== "") return num;
|
|
55
|
+
return value;
|
|
71
56
|
}
|
|
72
57
|
/**
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
obj[key] = value;
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
return obj;
|
|
106
|
-
});
|
|
107
|
-
if (opts.skipEmptyLines) {
|
|
108
|
-
rows = rows.filter((row) => Object.values(row).some((v) => v !== '' && v !== null && v !== undefined));
|
|
109
|
-
}
|
|
110
|
-
return rows;
|
|
58
|
+
* Parse CSV string into array of objects with headers as keys
|
|
59
|
+
* @param csv - CSV string to parse
|
|
60
|
+
* @param options - Optional parse configuration
|
|
61
|
+
* @returns Array of objects representing rows
|
|
62
|
+
* @throws Error if parsing fails
|
|
63
|
+
*/
|
|
64
|
+
function parseCsv(csv, options) {
|
|
65
|
+
if (options?.maxSize !== void 0 && csv.length > options.maxSize) throw new Error(`Input exceeds maximum allowed size: ${csv.length} bytes exceeds limit of ${options.maxSize} bytes`);
|
|
66
|
+
const opts = {
|
|
67
|
+
...DEFAULT_PARSE_OPTIONS,
|
|
68
|
+
...options
|
|
69
|
+
};
|
|
70
|
+
const trimmed = csv.trim();
|
|
71
|
+
validateQuoting(trimmed);
|
|
72
|
+
const parsed = dsvFormat(opts.delimiter ?? ",").parse(trimmed);
|
|
73
|
+
const { columns } = parsed;
|
|
74
|
+
const headers = opts.transformHeader ? columns.map((col, i) => opts.transformHeader(col, i)) : columns;
|
|
75
|
+
let rows = parsed.map((row) => {
|
|
76
|
+
const obj = {};
|
|
77
|
+
columns.forEach((col, i) => {
|
|
78
|
+
let value = row[col] ?? "";
|
|
79
|
+
if (opts.dynamicTyping && typeof value === "string") value = dynamicTypeConvert(value);
|
|
80
|
+
const key = headers[i];
|
|
81
|
+
if (key !== void 0) obj[key] = value;
|
|
82
|
+
});
|
|
83
|
+
return obj;
|
|
84
|
+
});
|
|
85
|
+
if (opts.skipEmptyLines) rows = rows.filter((row) => Object.values(row).some((v) => v !== "" && v !== null && v !== void 0));
|
|
86
|
+
return rows;
|
|
111
87
|
}
|
|
112
88
|
/**
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
return rows;
|
|
89
|
+
* Parse CSV string into array of arrays without treating first row as headers
|
|
90
|
+
* @param csv - CSV string to parse
|
|
91
|
+
* @param options - Optional parse configuration
|
|
92
|
+
* @returns Array of arrays representing rows
|
|
93
|
+
* @throws Error if parsing fails
|
|
94
|
+
*/
|
|
95
|
+
function parseCsvArray(csv, options) {
|
|
96
|
+
if (options?.maxSize !== void 0 && csv.length > options.maxSize) throw new Error(`Input exceeds maximum allowed size: ${csv.length} bytes exceeds limit of ${options.maxSize} bytes`);
|
|
97
|
+
const opts = {
|
|
98
|
+
...DEFAULT_PARSE_ARRAY_OPTIONS,
|
|
99
|
+
...options
|
|
100
|
+
};
|
|
101
|
+
const trimmed = csv.trim();
|
|
102
|
+
validateQuoting(trimmed);
|
|
103
|
+
let rows = dsvFormat(opts.delimiter ?? ",").parseRows(trimmed);
|
|
104
|
+
if (opts.skipEmptyLines) rows = rows.filter((row) => !(row.length === 1 && row[0] === ""));
|
|
105
|
+
return rows;
|
|
132
106
|
}
|
|
133
107
|
/**
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
if (opts.newline && opts.newline !== '\n') {
|
|
162
|
-
result = result.replace(/\n/g, opts.newline);
|
|
163
|
-
}
|
|
164
|
-
return result;
|
|
108
|
+
* Build CSV string from array of objects
|
|
109
|
+
*
|
|
110
|
+
* SECURITY: Set sanitize=true when data may contain user-controlled content
|
|
111
|
+
* to prevent CSV formula injection attacks in Excel/LibreOffice.
|
|
112
|
+
*
|
|
113
|
+
* @param data - Array of objects to convert to CSV
|
|
114
|
+
* @param options - Optional build configuration and sanitize flag
|
|
115
|
+
* @returns CSV string
|
|
116
|
+
*/
|
|
117
|
+
function buildCsv(data, options) {
|
|
118
|
+
const { sanitize, ...buildOpts } = options || {};
|
|
119
|
+
const opts = {
|
|
120
|
+
...DEFAULT_BUILD_OPTIONS,
|
|
121
|
+
...buildOpts
|
|
122
|
+
};
|
|
123
|
+
if (data.length === 0) return "";
|
|
124
|
+
let processedData = data;
|
|
125
|
+
if (sanitize) processedData = data.map((row) => sanitizeCsvObject(row));
|
|
126
|
+
const dsv = dsvFormat(opts.delimiter ?? ",");
|
|
127
|
+
let result;
|
|
128
|
+
if (opts.header === false) {
|
|
129
|
+
const rows = processedData.map((row) => Object.values(row).map(String));
|
|
130
|
+
result = dsv.formatRows(rows);
|
|
131
|
+
} else result = dsv.format(processedData);
|
|
132
|
+
if (opts.newline && opts.newline !== "\n") result = result.replace(/\n/g, opts.newline);
|
|
133
|
+
return result;
|
|
165
134
|
}
|
|
166
135
|
/**
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
return result;
|
|
136
|
+
* Build CSV string from array of arrays
|
|
137
|
+
*
|
|
138
|
+
* SECURITY: Set sanitize=true when data may contain user-controlled content
|
|
139
|
+
* to prevent CSV formula injection attacks in Excel/LibreOffice.
|
|
140
|
+
*
|
|
141
|
+
* @param data - Array of arrays to convert to CSV
|
|
142
|
+
* @param options - Optional build configuration and sanitize flag
|
|
143
|
+
* @returns CSV string
|
|
144
|
+
*/
|
|
145
|
+
function buildCsvArray(data, options) {
|
|
146
|
+
const { sanitize, ...buildOpts } = options || {};
|
|
147
|
+
const opts = {
|
|
148
|
+
...DEFAULT_BUILD_OPTIONS,
|
|
149
|
+
header: false,
|
|
150
|
+
...buildOpts
|
|
151
|
+
};
|
|
152
|
+
if (data.length === 0) return "";
|
|
153
|
+
let processedData = data;
|
|
154
|
+
if (sanitize) processedData = data.map((row) => sanitizeCsvArray(row));
|
|
155
|
+
let result = dsvFormat(opts.delimiter ?? ",").formatRows(processedData);
|
|
156
|
+
if (opts.newline && opts.newline !== "\n") result = result.replace(/\n/g, opts.newline);
|
|
157
|
+
return result;
|
|
191
158
|
}
|
|
192
159
|
/**
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
catch {
|
|
208
|
-
return false;
|
|
209
|
-
}
|
|
210
|
-
return true;
|
|
160
|
+
* Validate CSV format
|
|
161
|
+
* @param csv - CSV string to validate
|
|
162
|
+
* @returns True if valid CSV, false otherwise
|
|
163
|
+
*/
|
|
164
|
+
function isValidCsv(csv) {
|
|
165
|
+
if (!csv || csv.trim().length === 0) return false;
|
|
166
|
+
const trimmed = csv.trim();
|
|
167
|
+
if (!trimmed.includes(",")) return false;
|
|
168
|
+
try {
|
|
169
|
+
validateQuoting(trimmed);
|
|
170
|
+
} catch {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
return true;
|
|
211
174
|
}
|
|
212
175
|
/**
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
return `'${str}`;
|
|
226
|
-
}
|
|
227
|
-
return str;
|
|
176
|
+
* Sanitize a single CSV value to prevent formula injection
|
|
177
|
+
*
|
|
178
|
+
* Prefixes values starting with =, +, -, @, |, or % with a single quote
|
|
179
|
+
* to prevent Excel/LibreOffice from interpreting them as formulas.
|
|
180
|
+
*
|
|
181
|
+
* @param value - Value to sanitize
|
|
182
|
+
* @returns Sanitized value
|
|
183
|
+
*/
|
|
184
|
+
function sanitizeCsvValue(value) {
|
|
185
|
+
const str = String(value);
|
|
186
|
+
if (/^[=+\-@|%]/.test(str)) return `'${str}`;
|
|
187
|
+
return str;
|
|
228
188
|
}
|
|
229
189
|
/**
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
190
|
+
* Sanitize all values in an array to prevent formula injection
|
|
191
|
+
*
|
|
192
|
+
* @param values - Array of values to sanitize
|
|
193
|
+
* @returns Array with sanitized values
|
|
194
|
+
*/
|
|
195
|
+
function sanitizeCsvArray(values) {
|
|
196
|
+
return values.map(sanitizeCsvValue);
|
|
237
197
|
}
|
|
238
198
|
/**
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
return sanitized;
|
|
199
|
+
* Sanitize all values in an object to prevent formula injection
|
|
200
|
+
*
|
|
201
|
+
* @param obj - Object with values to sanitize
|
|
202
|
+
* @returns New object with sanitized values
|
|
203
|
+
*/
|
|
204
|
+
function sanitizeCsvObject(obj) {
|
|
205
|
+
const sanitized = {};
|
|
206
|
+
for (const [key, value] of Object.entries(obj)) sanitized[key] = sanitizeCsvValue(value);
|
|
207
|
+
return sanitized;
|
|
250
208
|
}
|
|
209
|
+
//#endregion
|
|
210
|
+
export { buildCsv, buildCsvArray, extractColumn as extractCsvColumn, findRows as findCsvRows, isValidCsv, parseCsv, parseCsvArray, sanitizeCsvArray, sanitizeCsvObject, sanitizeCsvValue };
|
|
211
|
+
|
|
251
212
|
//# sourceMappingURL=index.js.map
|