Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 67 additions & 74 deletions lib/src/type_handlers/directory_type_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,87 +10,81 @@ import '../extensions/request_helpers.dart';
import '../extensions/response_helpers.dart';
import 'type_handler.dart';

TypeHandler get directoryTypeHandler =>
TypeHandler<Directory>((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<Directory>((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!)}';
var filePath = '${directory.path}${sep}${Uri.decodeComponent(virtualPath!)}';

req.preventTraversal(filePath, directory);
filePath = filePath.replaceAll('/', Platform.pathSeparator);

req.log(() => 'Resolve virtual path: $virtualPath');
req.preventTraversal(filePath, directory);

final fileCandidates = <File>[
File(filePath),
File('$filePath/index.html'),
File('$filePath/index.htm'),
];
req.log(() => 'Resolve virtual path: $virtualPath');

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<int>);
final publicPath =
"${req.requestedUri.toString() + (virtualPath != null ? '/$virtualPath' : '')}/$fileName";
req.log(() => 'Uploaded file $publicPath');

await res.json({'path': publicPath});
}
final fileCandidates = <File>[File(filePath), File('${filePath}index.html'), File('${filePath}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}${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<int>);
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
Expand All @@ -101,8 +95,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;
Expand Down