qvdjs 0.9.1 → 0.9.2
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/dist/index.cjs +72 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +71 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var fs = require('fs');
|
|
3
4
|
var path = require('path');
|
|
4
|
-
var fs2 = require('fs');
|
|
5
5
|
var crypto = require('crypto');
|
|
6
6
|
var xml2 = require('xml2js');
|
|
7
7
|
var assert = require('assert');
|
|
@@ -10,8 +10,8 @@ var v8 = require('v8');
|
|
|
10
10
|
|
|
11
11
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
12
|
|
|
13
|
+
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
13
14
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
14
|
-
var fs2__default = /*#__PURE__*/_interopDefault(fs2);
|
|
15
15
|
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
16
16
|
var xml2__default = /*#__PURE__*/_interopDefault(xml2);
|
|
17
17
|
var assert__default = /*#__PURE__*/_interopDefault(assert);
|
|
@@ -262,8 +262,8 @@ var init_QvdSymbol = __esm({
|
|
|
262
262
|
};
|
|
263
263
|
}
|
|
264
264
|
});
|
|
265
|
-
function
|
|
266
|
-
const isCaseInsensitiveFS = process.platform === "win32"
|
|
265
|
+
function isWithinDirectoryLexically(resolvedBaseDir, resolvedPath) {
|
|
266
|
+
const isCaseInsensitiveFS = process.platform === "win32";
|
|
267
267
|
const base = isCaseInsensitiveFS ? resolvedBaseDir.toLowerCase() : resolvedBaseDir;
|
|
268
268
|
const target = isCaseInsensitiveFS ? resolvedPath.toLowerCase() : resolvedPath;
|
|
269
269
|
const relative = path__default.default.relative(base, target);
|
|
@@ -275,6 +275,55 @@ function isWithinDirectory(resolvedBaseDir, resolvedPath) {
|
|
|
275
275
|
}
|
|
276
276
|
return relative !== ".." && !relative.startsWith(`..${path__default.default.sep}`);
|
|
277
277
|
}
|
|
278
|
+
function resolveDeepestExisting(target) {
|
|
279
|
+
let current = target;
|
|
280
|
+
for (; ; ) {
|
|
281
|
+
try {
|
|
282
|
+
return fs__default.default.realpathSync(current);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
const code = (
|
|
285
|
+
/** @type {{code?: string}} */
|
|
286
|
+
error?.code
|
|
287
|
+
);
|
|
288
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
const parent = path__default.default.dirname(current);
|
|
292
|
+
if (parent === current) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
current = parent;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function isWithinDirectoryOnDisk(resolvedBaseDir, resolvedPath) {
|
|
300
|
+
let baseStat;
|
|
301
|
+
try {
|
|
302
|
+
baseStat = fs__default.default.statSync(fs__default.default.realpathSync(resolvedBaseDir));
|
|
303
|
+
} catch {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
let current = resolveDeepestExisting(resolvedPath);
|
|
307
|
+
if (current === null) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
for (; ; ) {
|
|
311
|
+
let stat;
|
|
312
|
+
try {
|
|
313
|
+
stat = fs__default.default.statSync(current);
|
|
314
|
+
} catch {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
if (stat.dev === baseStat.dev && stat.ino === baseStat.ino) {
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
const parent = path__default.default.dirname(current);
|
|
321
|
+
if (parent === current) {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
current = parent;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
278
327
|
function validatePath(filePath, allowedDir) {
|
|
279
328
|
if (typeof filePath !== "string" || filePath.length === 0) {
|
|
280
329
|
throw new exports.QvdValidationError("filePath must be a non-empty string", {
|
|
@@ -297,12 +346,17 @@ function validatePath(filePath, allowedDir) {
|
|
|
297
346
|
const resolvedPath = path__default.default.resolve(filePath);
|
|
298
347
|
const baseDir = allowedDir || process.cwd();
|
|
299
348
|
const resolvedBaseDir = path__default.default.resolve(baseDir);
|
|
300
|
-
|
|
349
|
+
const onDisk = isWithinDirectoryOnDisk(resolvedBaseDir, resolvedPath);
|
|
350
|
+
const contained = onDisk === null ? isWithinDirectoryLexically(resolvedBaseDir, resolvedPath) : onDisk;
|
|
351
|
+
if (!contained) {
|
|
301
352
|
throw new exports.QvdSecurityError("Path traversal detected: Access denied", {
|
|
302
353
|
path: filePath,
|
|
303
354
|
resolvedPath,
|
|
304
355
|
allowedDir: resolvedBaseDir,
|
|
305
|
-
reason: "outside_allowed_directory"
|
|
356
|
+
reason: "outside_allowed_directory",
|
|
357
|
+
// Says which check refused, so a rejection of a path that looks contained is traceable to
|
|
358
|
+
// a symlink or a case difference rather than looking like a bug.
|
|
359
|
+
check: onDisk === null ? "lexical" : "filesystem"
|
|
306
360
|
});
|
|
307
361
|
}
|
|
308
362
|
return resolvedPath;
|
|
@@ -331,7 +385,8 @@ var init_QvdFileWriter = __esm({
|
|
|
331
385
|
* @param {QvdDataFrame} df The data frame to write to the QVD file.
|
|
332
386
|
* @param {Object} [options={}] Options for the writer.
|
|
333
387
|
* @param {string} [options.allowedDir] Optional allowed directory path. If provided, the file
|
|
334
|
-
* path must be within this directory
|
|
388
|
+
* path must be within this directory, with symlinks resolved first, so a link inside it that
|
|
389
|
+
* points outside it is rejected. Defaults to the current working directory. To permit
|
|
335
390
|
* an entire volume, pass its root explicitly ('/' on POSIX, 'C:\\' on Windows); a null or
|
|
336
391
|
* empty value falls back to the working directory rather than removing the restriction.
|
|
337
392
|
* @param {Function} [options.onProgress] Optional progress callback function.
|
|
@@ -379,7 +434,7 @@ var init_QvdFileWriter = __esm({
|
|
|
379
434
|
const headerBuffer = Buffer.concat([Buffer.from(this._header, "utf-8"), Buffer.from([0])]);
|
|
380
435
|
let fd;
|
|
381
436
|
try {
|
|
382
|
-
fd = await
|
|
437
|
+
fd = await fs__default.default.promises.open(this._path, "w");
|
|
383
438
|
await fd.write(headerBuffer, 0, headerBuffer.length, 0);
|
|
384
439
|
await fd.write(this._symbolBuffer, 0, this._symbolBuffer.length, headerBuffer.length);
|
|
385
440
|
await fd.write(this._indexBuffer, 0, this._indexBuffer.length, headerBuffer.length + this._symbolBuffer.length);
|
|
@@ -1242,7 +1297,8 @@ var init_QvdFileReader = __esm({
|
|
|
1242
1297
|
* @param {string} filePath The path to the QVD file to load.
|
|
1243
1298
|
* @param {Object} [options={}] Options for the reader.
|
|
1244
1299
|
* @param {string} [options.allowedDir] Optional allowed directory path. If provided, the file
|
|
1245
|
-
* path must be within this directory
|
|
1300
|
+
* path must be within this directory, with symlinks resolved first, so a link inside it that
|
|
1301
|
+
* points outside it is rejected. Defaults to the current working directory. To permit
|
|
1246
1302
|
* an entire volume, pass its root explicitly ('/' on POSIX, 'C:\\' on Windows); a null or
|
|
1247
1303
|
* empty value falls back to the working directory rather than removing the restriction.
|
|
1248
1304
|
* @param {number} [options.memorySafetyFactor=0.3] Memory safety factor (0.0-1.0). Determines
|
|
@@ -1291,13 +1347,13 @@ var init_QvdFileReader = __esm({
|
|
|
1291
1347
|
*/
|
|
1292
1348
|
async _readData(maxRows = null) {
|
|
1293
1349
|
if (maxRows === null) {
|
|
1294
|
-
this._buffer = await
|
|
1350
|
+
this._buffer = await fs__default.default.promises.readFile(this._path);
|
|
1295
1351
|
this._fileSize = this._buffer.length;
|
|
1296
1352
|
return;
|
|
1297
1353
|
}
|
|
1298
1354
|
const HEADER_DELIMITER = "\r\n\0";
|
|
1299
1355
|
const CHUNK_SIZE = 64 * 1024;
|
|
1300
|
-
const stream =
|
|
1356
|
+
const stream = fs__default.default.createReadStream(this._path, {
|
|
1301
1357
|
highWaterMark: CHUNK_SIZE
|
|
1302
1358
|
});
|
|
1303
1359
|
const headerChunks = [];
|
|
@@ -1380,7 +1436,7 @@ var init_QvdFileReader = __esm({
|
|
|
1380
1436
|
}
|
|
1381
1437
|
const indexTableBytesToRead = rowsToLoad * recordSize;
|
|
1382
1438
|
const totalBytesToRead = indexTableOffset + indexTableBytesToRead;
|
|
1383
|
-
const fd = await
|
|
1439
|
+
const fd = await fs__default.default.promises.open(this._path, "r");
|
|
1384
1440
|
try {
|
|
1385
1441
|
const { size: fileSize } = await fd.stat();
|
|
1386
1442
|
this._fileSize = fileSize;
|
|
@@ -2126,7 +2182,8 @@ var init_QvdDataFrame = __esm({
|
|
|
2126
2182
|
* @param {string} path The path to the QVD file.
|
|
2127
2183
|
* @param {Object} [options] Optional writing options.
|
|
2128
2184
|
* @param {string} [options.allowedDir] Optional allowed directory path. If provided, the file path
|
|
2129
|
-
* must be within this directory
|
|
2185
|
+
* must be within this directory, with symlinks resolved first, so a link inside it that points
|
|
2186
|
+
* outside it is rejected. Defaults to the current working directory. To permit an entire
|
|
2130
2187
|
* volume, pass its root explicitly ('/' on POSIX, 'C:\\' on Windows); a null or empty value falls
|
|
2131
2188
|
* back to the working directory rather than removing the restriction.
|
|
2132
2189
|
* @param {Function} [options.onProgress] Optional progress callback function that receives progress updates during write operations.
|
|
@@ -2147,7 +2204,8 @@ var init_QvdDataFrame = __esm({
|
|
|
2147
2204
|
* @param {number|null} [options.maxRows] The maximum number of rows to load. Must be a non-negative
|
|
2148
2205
|
* integer; if not specified or null, all rows are loaded. Anything else throws a QvdValidationError.
|
|
2149
2206
|
* @param {string} [options.allowedDir] Optional allowed directory path. If provided, the file path
|
|
2150
|
-
* must be within this directory
|
|
2207
|
+
* must be within this directory, with symlinks resolved first, so a link inside it that points
|
|
2208
|
+
* outside it is rejected. Defaults to the current working directory. To permit an entire
|
|
2151
2209
|
* volume, pass its root explicitly ('/' on POSIX, 'C:\\' on Windows); a null or empty value falls
|
|
2152
2210
|
* back to the working directory rather than removing the restriction.
|
|
2153
2211
|
* @param {number} [options.memorySafetyFactor=0.3] Memory safety factor (0.0-1.0). Determines what percentage
|