mirror of
https://github.com/localsend/localsend.git
synced 2026-08-07 07:14:52 +00:00
fix: create the destination directory when it is missing (#3223)
This commit is contained in:
@@ -215,14 +215,14 @@ Future<(String, String?, String)> digestFilePathAndPrepareDirectory({
|
||||
if (!p.isWithin(parentDirectory, dir)) {
|
||||
throw 'Path traversal detected';
|
||||
}
|
||||
|
||||
try {
|
||||
Directory(dir).createSync(recursive: true);
|
||||
} catch (e) {
|
||||
_logger.warning('Could not create directory', e);
|
||||
}
|
||||
}
|
||||
|
||||
// The destination directory may not exist anymore, e.g. because it was deleted
|
||||
// or is on a drive that is no longer mounted. This also creates the
|
||||
// sub-directories of a folder transfer. Errors are propagated so that the
|
||||
// caller fails the upload instead of writing to a path that cannot be opened.
|
||||
Directory(dir).createSync(recursive: true);
|
||||
|
||||
String destinationPath;
|
||||
int counter = 1;
|
||||
do {
|
||||
|
||||
@@ -41,6 +41,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.1"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
build:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -113,6 +121,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -161,6 +177,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.7"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -214,6 +238,11 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.12.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@@ -291,6 +320,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.12.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
legalize:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -315,6 +368,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.19"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -575,6 +636,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
type_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -614,6 +683,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.2.0"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -33,4 +33,6 @@ dev_dependencies:
|
||||
build_runner: 2.15.1
|
||||
dart_mappable_builder: 4.8.0
|
||||
flutter_lints: 6.0.0
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
freezed: 3.2.5
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:localsend_isolates/src/task/server/file_saver.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
void main() {
|
||||
late Directory tempDir;
|
||||
|
||||
setUp(() {
|
||||
tempDir = Directory.systemTemp.createTempSync('file_saver_test');
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
if (tempDir.existsSync()) {
|
||||
tempDir.deleteSync(recursive: true);
|
||||
}
|
||||
});
|
||||
|
||||
Future<String> digest(String parentDirectory, String fileName) async {
|
||||
final (path, _, _) = await digestFilePathAndPrepareDirectory(
|
||||
parentDirectory: parentDirectory,
|
||||
fileName: fileName,
|
||||
createdDirectories: {},
|
||||
);
|
||||
return path;
|
||||
}
|
||||
|
||||
test('creates the destination directory when it does not exist', () async {
|
||||
final destination = p.join(tempDir.path, 'gone');
|
||||
final path = await digest(destination, 'file.txt');
|
||||
|
||||
expect(Directory(destination).existsSync(), isTrue);
|
||||
expect(path, p.join(destination, 'file.txt'));
|
||||
});
|
||||
|
||||
test('creates the sub-directories of a folder transfer', () async {
|
||||
final path = await digest(tempDir.path, p.join('outer', 'inner', 'file.txt'));
|
||||
|
||||
expect(Directory(p.join(tempDir.path, 'outer', 'inner')).existsSync(), isTrue);
|
||||
expect(path, p.join(tempDir.path, 'outer', 'inner', 'file.txt'));
|
||||
});
|
||||
|
||||
test('keeps an existing directory and its content', () async {
|
||||
File(p.join(tempDir.path, 'file.txt')).writeAsStringSync('hello');
|
||||
|
||||
final path = await digest(tempDir.path, 'file.txt');
|
||||
|
||||
expect(path, p.join(tempDir.path, 'file (2).txt'));
|
||||
expect(File(p.join(tempDir.path, 'file.txt')).readAsStringSync(), 'hello');
|
||||
});
|
||||
|
||||
test('still rejects path traversal', () async {
|
||||
await expectLater(
|
||||
digest(tempDir.path, p.join('..', 'escaped', 'file.txt')),
|
||||
throwsA('Path traversal detected'),
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user