mirror of
https://github.com/DumbWareio/DumbDrop.git
synced 2025-11-15 19:31:25 +00:00
(#73)
* fix: resolve file disappearance with Docker bind mounts
Files were disappearing when using bind mounts because fs.realpathSync()
requires files to exist. Updated path validation to use path.resolve()
for non-existing files (during upload) and fs.realpathSync() only for
existing files (during operations).
- Add isPathWithinUploadDir() to fileUtils with requireExists parameter
- Update files.js to use shared validation function
- Add path validation to upload.js for all file operations
- Add comprehensive test suite (16 tests, all passing)
- Maintain security against path traversal attacks
- Full backward compatibility with named volumes
Fixes: Files disappearing with bind mounts
Related: d69a8b2, fc8bff9"
* Improve file existence check in isPathWithinUploadDir
Refines logic to immediately return false if requireExists is true and the file does not exist, ensuring more robust handling of non-existent files.
* Bump version to 1.0.1
Update package version from 1.0.0 to 1.0.1 in preparation for a new release.
153 lines
5.7 KiB
JavaScript
153 lines
5.7 KiB
JavaScript
/**
|
|
* Path validation tests for bind mount compatibility
|
|
* Tests the isPathWithinUploadDir function with various scenarios
|
|
* Validates both existing and non-existing file paths
|
|
*/
|
|
|
|
const { test, describe, before, after } = require('node:test');
|
|
const assert = require('node:assert');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const { isPathWithinUploadDir } = require('../src/utils/fileUtils');
|
|
|
|
describe('Path Validation for Bind Mounts', () => {
|
|
let testUploadDir;
|
|
|
|
before(() => {
|
|
// Create a temporary upload directory for testing
|
|
testUploadDir = path.join(os.tmpdir(), 'dumbdrop-test-uploads-' + Date.now());
|
|
fs.mkdirSync(testUploadDir, { recursive: true });
|
|
});
|
|
|
|
after(() => {
|
|
// Clean up test directory
|
|
try {
|
|
fs.rmSync(testUploadDir, { recursive: true, force: true });
|
|
} catch (err) {
|
|
console.error('Failed to clean up test directory:', err);
|
|
}
|
|
});
|
|
|
|
test('should allow valid file path within upload directory (non-existent file)', () => {
|
|
const filePath = path.join(testUploadDir, 'test-file.txt');
|
|
assert.strictEqual(isPathWithinUploadDir(filePath, testUploadDir, false), true);
|
|
});
|
|
|
|
test('should allow valid nested file path within upload directory (non-existent)', () => {
|
|
const filePath = path.join(testUploadDir, 'subfolder', 'test-file.txt');
|
|
assert.strictEqual(isPathWithinUploadDir(filePath, testUploadDir, false), true);
|
|
});
|
|
|
|
test('should allow valid file path with spaces (non-existent)', () => {
|
|
const filePath = path.join(testUploadDir, 'test file with spaces.txt');
|
|
assert.strictEqual(isPathWithinUploadDir(filePath, testUploadDir, false), true);
|
|
});
|
|
|
|
test('should reject path traversal with ../ (non-existent)', () => {
|
|
const filePath = path.join(testUploadDir, '..', 'malicious.txt');
|
|
assert.strictEqual(isPathWithinUploadDir(filePath, testUploadDir, false), false);
|
|
});
|
|
|
|
test('should reject path traversal with nested ../ (non-existent)', () => {
|
|
const filePath = path.join(testUploadDir, 'folder', '..', '..', 'malicious.txt');
|
|
assert.strictEqual(isPathWithinUploadDir(filePath, testUploadDir, false), false);
|
|
});
|
|
|
|
test('should allow upload directory itself', () => {
|
|
assert.strictEqual(isPathWithinUploadDir(testUploadDir, testUploadDir, false), true);
|
|
});
|
|
|
|
test('should work with .partial file extensions', () => {
|
|
const filePath = path.join(testUploadDir, 'upload.txt.partial');
|
|
assert.strictEqual(isPathWithinUploadDir(filePath, testUploadDir, false), true);
|
|
});
|
|
|
|
test('should handle paths with normalized separators', () => {
|
|
// Test with forward slashes (cross-platform)
|
|
const filePath = path.normalize(path.join(testUploadDir, 'folder/subfolder/file.txt'));
|
|
assert.strictEqual(isPathWithinUploadDir(filePath, testUploadDir, false), true);
|
|
});
|
|
|
|
test('should work with existing files when requireExists=true', () => {
|
|
// Create an actual file
|
|
const filePath = path.join(testUploadDir, 'existing-file.txt');
|
|
fs.writeFileSync(filePath, 'test content');
|
|
|
|
assert.strictEqual(isPathWithinUploadDir(filePath, testUploadDir, true), true);
|
|
|
|
// Clean up
|
|
fs.unlinkSync(filePath);
|
|
});
|
|
|
|
test('should reject existing file outside upload directory', () => {
|
|
// Create a file outside the upload directory
|
|
const outsideFile = path.join(os.tmpdir(), 'outside-file.txt');
|
|
fs.writeFileSync(outsideFile, 'test content');
|
|
|
|
assert.strictEqual(isPathWithinUploadDir(outsideFile, testUploadDir, true), false);
|
|
|
|
// Clean up
|
|
fs.unlinkSync(outsideFile);
|
|
});
|
|
|
|
test('should reject paths on different drives (Windows only)', () => {
|
|
if (process.platform !== 'win32') {
|
|
return; // Skip on non-Windows
|
|
}
|
|
|
|
// Try to use a different drive letter
|
|
const currentDrive = testUploadDir.split(':')[0];
|
|
const differentDrive = currentDrive === 'C' ? 'D' : 'C';
|
|
const differentDrivePath = `${differentDrive}:\\temp\\file.txt`;
|
|
|
|
// This should be rejected
|
|
assert.strictEqual(isPathWithinUploadDir(differentDrivePath, testUploadDir, false), false);
|
|
});
|
|
|
|
test('should handle deeply nested folder structures', () => {
|
|
const deepPath = path.join(testUploadDir, 'a', 'b', 'c', 'd', 'e', 'file.txt');
|
|
assert.strictEqual(isPathWithinUploadDir(deepPath, testUploadDir, false), true);
|
|
});
|
|
|
|
test('should reject absolute paths outside upload directory', () => {
|
|
const outsidePath = path.join(os.tmpdir(), 'outside', 'file.txt');
|
|
assert.strictEqual(isPathWithinUploadDir(outsidePath, testUploadDir, false), false);
|
|
});
|
|
});
|
|
|
|
describe('Path Validation Edge Cases', () => {
|
|
let testUploadDir;
|
|
|
|
before(() => {
|
|
testUploadDir = path.join(os.tmpdir(), 'dumbdrop-edge-test-' + Date.now());
|
|
fs.mkdirSync(testUploadDir, { recursive: true });
|
|
});
|
|
|
|
after(() => {
|
|
try {
|
|
fs.rmSync(testUploadDir, { recursive: true, force: true });
|
|
} catch (err) {
|
|
console.error('Failed to clean up test directory:', err);
|
|
}
|
|
});
|
|
|
|
test('should handle special characters in filenames', () => {
|
|
const filePath = path.join(testUploadDir, 'file (1).txt');
|
|
assert.strictEqual(isPathWithinUploadDir(filePath, testUploadDir, false), true);
|
|
});
|
|
|
|
test('should handle Unicode filenames', () => {
|
|
const filePath = path.join(testUploadDir, 'файл.txt'); // Russian
|
|
assert.strictEqual(isPathWithinUploadDir(filePath, testUploadDir, false), true);
|
|
});
|
|
|
|
test('should reject non-existent upload directory', () => {
|
|
const fakeUploadDir = path.join(os.tmpdir(), 'non-existent-dir-' + Date.now());
|
|
const filePath = path.join(fakeUploadDir, 'file.txt');
|
|
|
|
assert.strictEqual(isPathWithinUploadDir(filePath, fakeUploadDir, false), false);
|
|
});
|
|
});
|
|
|