From ec9d6a1adecfc84533b19f8ffe9da28df7d741d6 Mon Sep 17 00:00:00 2001 From: Colin Constable Date: Fri, 11 Apr 2025 13:57:56 -0700 Subject: [PATCH 1/4] fix:directory traversal for windows --- .../type_handlers/directory_type_handler.dart | 139 ++++++++---------- 1 file changed, 65 insertions(+), 74 deletions(-) diff --git a/lib/src/type_handlers/directory_type_handler.dart b/lib/src/type_handlers/directory_type_handler.dart index 0b869a4..3e402d3 100644 --- a/lib/src/type_handlers/directory_type_handler.dart +++ b/lib/src/type_handlers/directory_type_handler.dart @@ -10,87 +10,79 @@ import '../extensions/request_helpers.dart'; import '../extensions/response_helpers.dart'; import 'type_handler.dart'; -TypeHandler get directoryTypeHandler => - TypeHandler((req, res, Directory directory) async { - directory = directory.absolute; - final usedRoute = req.route; - String? virtualPath; - if (usedRoute.contains('*')) { - virtualPath = req.uri.path - .substring(min(req.uri.path.length, usedRoute.indexOf('*'))); - } +TypeHandler get directoryTypeHandler => TypeHandler((req, res, Directory directory) async { + directory = directory.absolute; + final usedRoute = req.route; + final sep = Platform.pathSeparator; + String? virtualPath; + if (usedRoute.contains('*')) { + virtualPath = req.uri.path.substring(min(req.uri.path.length, usedRoute.indexOf('*'))); + } - if (req.method == 'GET' || req.method == 'HEAD') { - assert(usedRoute.contains('*'), - 'TypeHandler of type Directory GET request needs a route declaration that contains a wildcard (*). Found: $usedRoute'); + if (req.method == 'GET' || req.method == 'HEAD') { + assert( + usedRoute.contains('*'), + 'TypeHandler of type Directory GET request needs a route declaration that contains a wildcard (*). Found: $usedRoute', + ); - final filePath = - '${directory.path}/${Uri.decodeComponent(virtualPath!)}'; + final filePath = '${directory.path}/${Uri.decodeComponent(virtualPath!)}'; - req.preventTraversal(filePath, directory); + req.preventTraversal(filePath, directory); - req.log(() => 'Resolve virtual path: $virtualPath'); + req.log(() => 'Resolve virtual path: $virtualPath'); - final fileCandidates = [ - File(filePath), - File('$filePath/index.html'), - File('$filePath/index.htm'), - ]; + final fileCandidates = [File(filePath), File('$filePath${sep}.html'), File('$filePath${sep}index.htm')]; - try { - var match = fileCandidates.firstWhere((file) => file.existsSync()); - req.log(() => 'Respond with file: ${match.path}'); - await _respondWithFile(res, match, headerOnly: req.method == 'HEAD'); - } on StateError { - req.log(() => - 'Could not match with any file. Expected file at: $filePath'); - } - } - if (req.method == 'POST' || req.method == 'PUT') { - //Upload file - final body = await req.body; - - if (body is Map && body['file'] is HttpBodyFileUpload) { - if (virtualPath != null) { - req.preventTraversal('${directory.path}/$virtualPath', directory); - directory = Directory('${directory.path}/$virtualPath').absolute; - } - if (await directory.exists() == false) { - await directory.create(recursive: true); - } - final fileName = (body['file'] as HttpBodyFileUpload).filename; - - final fileToWrite = File('${directory.path}/$fileName'); - - req.preventTraversal(fileToWrite.path, directory); - - await fileToWrite.writeAsBytes( - (body['file'] as HttpBodyFileUpload).content as List); - final publicPath = - "${req.requestedUri.toString() + (virtualPath != null ? '/$virtualPath' : '')}/$fileName"; - req.log(() => 'Uploaded file $publicPath'); - - await res.json({'path': publicPath}); - } + try { + var match = fileCandidates.firstWhere((file) => file.existsSync()); + req.log(() => 'Respond with file: ${match.path}'); + await _respondWithFile(res, match, headerOnly: req.method == 'HEAD'); + } on StateError { + req.log(() => 'Could not match with any file. Expected file at: $filePath'); + } + } + if (req.method == 'POST' || req.method == 'PUT') { + //Upload file + final body = await req.body; + + if (body is Map && body['file'] is HttpBodyFileUpload) { + if (virtualPath != null) { + req.preventTraversal('${directory.path}${sep}$virtualPath', directory); + directory = Directory('${directory.path}${sep}$virtualPath').absolute; } - if (req.method == 'DELETE') { - final fileToDelete = - File('${directory.path}/${Uri.decodeComponent(virtualPath!)}'); - - req.preventTraversal(fileToDelete.path, directory); - - if (await fileToDelete.exists()) { - await fileToDelete.delete(); - await res.json({'success': 'true'}); - } else { - res.statusCode = 404; - await res.json({'error': 'file not found'}); - } + if (await directory.exists() == false) { + await directory.create(recursive: true); } - }); + final fileName = (body['file'] as HttpBodyFileUpload).filename; + + final fileToWrite = File('${directory.path}${sep}$fileName'); + + req.preventTraversal(fileToWrite.path, directory); + + await fileToWrite.writeAsBytes((body['file'] as HttpBodyFileUpload).content as List); + final publicPath = + "${req.requestedUri.toString() + (virtualPath != null ? '${sep}$virtualPath' : '')}${sep}$fileName"; + req.log(() => 'Uploaded file $publicPath'); + + await res.json({'path': publicPath}); + } + } + if (req.method == 'DELETE') { + final fileToDelete = File('${directory.path}${sep}${Uri.decodeComponent(virtualPath!)}'); + + req.preventTraversal(fileToDelete.path, directory); + + if (await fileToDelete.exists()) { + await fileToDelete.delete(); + await res.json({'success': 'true'}); + } else { + res.statusCode = 404; + await res.json({'error': 'file not found'}); + } + } +}); -Future _respondWithFile(HttpResponse res, File file, - {bool headerOnly = false}) async { +Future _respondWithFile(HttpResponse res, File file, {bool headerOnly = false}) async { res.setContentTypeFromFile(file); // This is necessary to deal with 'HEAD' requests @@ -101,8 +93,7 @@ Future _respondWithFile(HttpResponse res, File file, } extension _Logger on HttpRequest { - void log(String Function() msgFn) => - alfred.logWriter(() => 'DirectoryTypeHandler: ${msgFn()}', LogType.debug); + void log(String Function() msgFn) => alfred.logWriter(() => 'DirectoryTypeHandler: ${msgFn()}', LogType.debug); void preventTraversal(String filePath, Directory absDir) { final check = File(filePath).absolute; From 3ea2f5cce7090cbc19cbf2f796e26d277b0fb44a Mon Sep 17 00:00:00 2001 From: Colin Constable Date: Fri, 11 Apr 2025 14:03:37 -0700 Subject: [PATCH 2/4] fix;last separator --- lib/src/type_handlers/directory_type_handler.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/type_handlers/directory_type_handler.dart b/lib/src/type_handlers/directory_type_handler.dart index 3e402d3..a8407de 100644 --- a/lib/src/type_handlers/directory_type_handler.dart +++ b/lib/src/type_handlers/directory_type_handler.dart @@ -25,7 +25,7 @@ TypeHandler get directoryTypeHandler => TypeHandler((req, res, Direct 'TypeHandler of type Directory GET request needs a route declaration that contains a wildcard (*). Found: $usedRoute', ); - final filePath = '${directory.path}/${Uri.decodeComponent(virtualPath!)}'; + final filePath = '${directory.path}${sep}${Uri.decodeComponent(virtualPath!)}'; req.preventTraversal(filePath, directory); From 3ab943fd3048ff64fc0841cde3e2d6cf415379ed Mon Sep 17 00:00:00 2001 From: Colin Constable Date: Fri, 11 Apr 2025 14:14:08 -0700 Subject: [PATCH 3/4] fix; index.html --- lib/src/type_handlers/directory_type_handler.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/type_handlers/directory_type_handler.dart b/lib/src/type_handlers/directory_type_handler.dart index a8407de..2a69a72 100644 --- a/lib/src/type_handlers/directory_type_handler.dart +++ b/lib/src/type_handlers/directory_type_handler.dart @@ -31,7 +31,7 @@ TypeHandler get directoryTypeHandler => TypeHandler((req, res, Direct req.log(() => 'Resolve virtual path: $virtualPath'); - final fileCandidates = [File(filePath), File('$filePath${sep}.html'), File('$filePath${sep}index.htm')]; + final fileCandidates = [File(filePath), File('$filePath${sep}index.html'), File('$filePath${sep}index.htm')]; try { var match = fileCandidates.firstWhere((file) => file.existsSync()); From 8ecf0437cb91d591d5b1e1547e662438d5f65351 Mon Sep 17 00:00:00 2001 From: Colin Constable Date: Fri, 11 Apr 2025 14:22:52 -0700 Subject: [PATCH 4/4] fix: filepaths --- lib/src/type_handlers/directory_type_handler.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/type_handlers/directory_type_handler.dart b/lib/src/type_handlers/directory_type_handler.dart index 2a69a72..7cd60e8 100644 --- a/lib/src/type_handlers/directory_type_handler.dart +++ b/lib/src/type_handlers/directory_type_handler.dart @@ -25,13 +25,15 @@ TypeHandler get directoryTypeHandler => TypeHandler((req, res, Direct 'TypeHandler of type Directory GET request needs a route declaration that contains a wildcard (*). Found: $usedRoute', ); - final filePath = '${directory.path}${sep}${Uri.decodeComponent(virtualPath!)}'; + var filePath = '${directory.path}${sep}${Uri.decodeComponent(virtualPath!)}'; + + filePath = filePath.replaceAll('/', Platform.pathSeparator); req.preventTraversal(filePath, directory); req.log(() => 'Resolve virtual path: $virtualPath'); - final fileCandidates = [File(filePath), File('$filePath${sep}index.html'), File('$filePath${sep}index.htm')]; + final fileCandidates = [File(filePath), File('${filePath}index.html'), File('${filePath}index.htm')]; try { var match = fileCandidates.firstWhere((file) => file.existsSync());