update: updating ports from the symfony http foundation component

This commit is contained in:
Patrick Stewart 2024-07-09 10:29:11 -07:00
parent 830fcdf0da
commit 0c7bc94cc3
12 changed files with 315 additions and 10 deletions

View file

@ -9,4 +9,5 @@
library; library;
export "src/foundation/file/file.dart";
export 'src/foundation/file/upload_file.dart'; export 'src/foundation/file/upload_file.dart';

View file

@ -16,5 +16,5 @@ export 'src/foundation/file/exception/file_not_found_exception.dart';
export 'src/foundation/file/exception/form_size_file_exception.dart'; export 'src/foundation/file/exception/form_size_file_exception.dart';
export 'src/foundation/file/exception/ini_size_file_exception.dart'; export 'src/foundation/file/exception/ini_size_file_exception.dart';
export 'src/foundation/file/exception/no_file_exception.dart'; export 'src/foundation/file/exception/no_file_exception.dart';
export 'src/foundation/file/exception/no_tmpdir_file_exception.dart'; export 'src/foundation/file/exception/no_tmp_dir_file_exception.dart';
export 'src/foundation/file/exception/partial_file_exception.dart'; export 'src/foundation/file/exception/partial_file_exception.dart';

View file

@ -12,5 +12,5 @@ import 'package:protevus_http/foundation_file_exception.dart';
/// @author Florent Mata <florentmata@gmail.com> /// @author Florent Mata <florentmata@gmail.com>
class CannotWriteFileException extends FileException { class CannotWriteFileException extends FileException {
/// Creates a new [CannotWriteFileException] with the given [message]. /// Creates a new [CannotWriteFileException] with the given [message].
CannotWriteFileException([String message = '']) : super(message); CannotWriteFileException([super.message]);
} }

View file

@ -12,5 +12,5 @@ import 'package:protevus_http/foundation_file_exception.dart';
/// @author Florent Mata <florentmata@gmail.com> /// @author Florent Mata <florentmata@gmail.com>
class ExtensionFileException extends FileException { class ExtensionFileException extends FileException {
// The constructor is empty as it inherits from FileException // The constructor is empty as it inherits from FileException
ExtensionFileException([String message = '']) : super(message); ExtensionFileException([super.message]);
} }

View file

@ -11,5 +11,5 @@ import 'package:protevus_http/foundation_file_exception.dart';
/// ///
/// @author Florent Mata <florentmata@gmail.com> /// @author Florent Mata <florentmata@gmail.com>
class FormSizeFileException extends FileException { class FormSizeFileException extends FileException {
FormSizeFileException([String message = '']) : super(message); FormSizeFileException([super.message]);
} }

View file

@ -11,5 +11,5 @@ import 'package:protevus_http/foundation_file_exception.dart';
/// ///
/// @author Florent Mata <florentmata@gmail.com> /// @author Florent Mata <florentmata@gmail.com>
class IniSizeFileException extends FileException { class IniSizeFileException extends FileException {
// No additional functionality needed, as this exception is just a specific type of FileException IniSizeFileException(super.message);
} }

View file

@ -11,6 +11,5 @@ import 'package:protevus_http/foundation_file_exception.dart';
/// ///
/// @author Florent Mata <florentmata@gmail.com> /// @author Florent Mata <florentmata@gmail.com>
class NoFileException extends FileException { class NoFileException extends FileException {
// In Dart, we don't need to explicitly declare an empty class body NoFileException(super.message);
// if there are no additional members or methods.
} }

View file

@ -4,5 +4,5 @@ import 'package:protevus_http/foundation_file_exception.dart';
/// ///
/// @author Florent Mata <florentmata@gmail.com> /// @author Florent Mata <florentmata@gmail.com>
class NoTmpDirFileException extends FileException { class NoTmpDirFileException extends FileException {
// The constructor is implicit in this case, as it doesn't require any additional setup NoTmpDirFileException(super.message);
} }

View file

@ -14,6 +14,5 @@ import 'package:protevus_http/foundation_file_exception.dart';
/// ///
/// @author Florent Mata <florentmata@gmail.com> /// @author Florent Mata <florentmata@gmail.com>
class PartialFileException extends FileException { class PartialFileException extends FileException {
// In Dart, we don't need to declare an empty class body if there are no additional PartialFileException(super.message);
// properties or methods. The class will inherit everything from FileException.
} }

View file

@ -0,0 +1,174 @@
import 'dart:io' as io;
import 'package:path/path.dart' as p;
import 'package:protevus_http/foundation_file_exception.dart';
import 'package:protevus_mime/mime.dart';
/// A file in the file system.
class File extends io.FileSystemEntity {
late final io.File _dartFile;
@override
final String path;
/// Constructs a new file from the given path.
///
/// [path] The path to the file
/// [checkPath] Whether to check the path or not
///
/// Throws [FileNotFoundException] If the given path is not a file
File(this.path, {bool checkPath = true}) {
if (checkPath && !io.FileSystemEntity.isFileSync(path)) {
throw FileNotFoundException(path);
}
_dartFile = io.File(path);
}
/// Returns the extension based on the mime type.
///
/// If the mime type is unknown, returns null.
///
/// This method uses the mime type as guessed by getMimeType()
/// to guess the file extension.
Future<String?> guessExtension() async {
final mimeType = await getMimeType();
if (mimeType == null) return null;
final extensions = MimeTypes.getDefault().getExtensions(mimeType);
return extensions.isNotEmpty ? extensions.first : null;
}
/// Returns the mime type of the file.
///
/// The mime type is guessed using the MimeTypes class.
Future<String?> getMimeType() {
return MimeTypes.getDefault().guessMimeType(path);
}
/// Moves the file to a new location.
///
/// Throws [FileException] if the target file could not be created
File move(String directory, [String? name]) {
final target = getTargetFile(directory, name);
try {
final newPath = target.path;
_dartFile.renameSync(newPath);
chmod(newPath, '0666');
return target;
} catch (e) {
throw FileException(
'Could not move the file "$path" to "${target.path}" ($e).');
}
}
/// Returns the content of the file.
String getContent() {
try {
return _dartFile.readAsStringSync();
} catch (e) {
throw FileException('Could not get the content of the file "$path".');
}
}
/// Returns the target file for a move operation.
File getTargetFile(String directory, [String? name]) {
final dir = io.Directory(directory);
if (!dir.existsSync()) {
try {
dir.createSync(recursive: true);
} catch (e) {
throw FileException('Unable to create the "$directory" directory.');
}
} else if (!dir.statSync().modeString().contains('w')) {
throw FileException('Unable to write in the "$directory" directory.');
}
final targetPath = p.join(directory, name ?? p.basename(path));
return File(targetPath, checkPath: false);
}
/// Returns locale independent base name of the given path.
String getName(String name) {
final normalizedName = name.replaceAll('\\', '/');
final pos = normalizedName.lastIndexOf('/');
return pos == -1 ? normalizedName : normalizedName.substring(pos + 1);
}
/// Changes the file permissions.
///
/// [filePath] is the path to the file whose permissions should be changed.
/// [mode] should be an octal string like '0644' for Unix-like systems.
/// For Windows, use 'read' for read-only, 'write' for read/write, or 'full' for full control.
static Future<void> chmod(String filePath, String mode) async {
if (io.Platform.isWindows) {
await _chmodWindows(filePath, mode);
} else {
await _chmodUnix(filePath, mode);
}
}
static Future<void> _chmodUnix(String filePath, String mode) async {
try {
final result = await io.Process.run('chmod', [mode, filePath]);
if (result.exitCode != 0) {
throw FileException(
'Failed to change permissions for $filePath: ${result.stderr}');
}
} catch (e) {
if (e.toString().contains('Permission denied')) {
// Optionally, you could try with sudo here, but that requires user interaction
throw FileException(
'Permission denied. You may need to run this with elevated privileges.');
} else {
throw FileException('Failed to change permissions for $filePath: $e');
}
}
}
static Future<void> _chmodWindows(String filePath, String mode) async {
String permission;
switch (mode.toLowerCase()) {
case 'read':
permission = '(R)';
break;
case 'write':
permission = '(R,W)';
break;
case 'full':
permission = '(F)';
break;
default:
throw ArgumentError(
'Invalid mode for Windows. Use "read", "write", or "full".');
}
final result = await io.Process.run(
'icacls', [filePath, '/grant', '*S-1-1-0:$permission']);
if (result.exitCode != 0) {
throw FileException(
'Failed to change permissions for $filePath: ${result.stderr}');
}
}
@override
io.FileSystemEntity get absolute => throw UnimplementedError();
@override
Future<bool> exists() {
throw UnimplementedError();
}
@override
bool existsSync() {
throw UnimplementedError();
}
@override
Future<io.FileSystemEntity> rename(String newPath) {
throw UnimplementedError();
}
@override
io.FileSystemEntity renameSync(String newPath) {
throw UnimplementedError();
}
}

View file

@ -0,0 +1,131 @@
import 'package:path/path.dart' as p;
import 'package:protevus_http/foundation_file_exception.dart';
import 'package:protevus_http/foundation_file.dart';
import 'package:protevus_mime/mime.dart';
/// A file uploaded through a form.
class UploadedFile extends File {
late String _originalName;
late String _mimeType;
late int _error;
late String _originalPath;
late bool _test;
UploadedFile(
super.path,
String originalName,
String? mimeType,
int? error,
bool test,
) {
_originalName = _getName(originalName);
_originalPath = originalName.replaceAll('\\', '/');
_mimeType = mimeType ?? 'application/octet-stream';
_error = error ?? 0; // UPLOAD_ERR_OK
_test = test;
}
String getClientOriginalName() {
return _originalName;
}
String getClientOriginalExtension() {
return p.extension(_originalName);
}
String getClientOriginalPath() {
return _originalPath;
}
String getClientMimeType() => _mimeType;
String? guessClientExtension() {
try {
List<String> extensions =
MimeTypes.getDefault().getExtensions(getClientMimeType());
return extensions.isNotEmpty ? extensions[0] : null;
} catch (e) {
return null;
}
}
int getError() {
return _error;
}
bool isValid() {
bool isOk = _error == 0; // UPLOAD_ERR_OK
return _test ? isOk : isOk && File(path).existsSync();
}
@override
File move(String directory, [String? name]) {
if (isValid()) {
if (_test) {
return File(p.join(directory, name ?? p.basename(path)));
}
File target = _getTargetFile(directory, name);
try {
return super.move(target.path);
} catch (e) {
throw FileException(
'Could not move the file "$path" to "${target.path}" ($e).');
}
}
switch (_error) {
case 1: // UPLOAD_ERR_INI_SIZE
throw IniSizeFileException(_getErrorMessage());
case 2: // UPLOAD_ERR_FORM_SIZE
throw FormSizeFileException(_getErrorMessage());
case 3: // UPLOAD_ERR_PARTIAL
throw PartialFileException(_getErrorMessage());
case 4: // UPLOAD_ERR_NO_FILE
throw NoFileException(_getErrorMessage());
case 6: // UPLOAD_ERR_NO_TMP_DIR
throw NoTmpDirFileException(_getErrorMessage());
case 7: // UPLOAD_ERR_CANT_WRITE
throw CannotWriteFileException(_getErrorMessage());
case 8: // UPLOAD_ERR_EXTENSION
throw ExtensionFileException(_getErrorMessage());
default:
throw FileException(_getErrorMessage());
}
}
static int getMaxFilesize() {
// Note: This is a placeholder. Implement according to your needs.
return 2 * 1024 * 1024; // 2MB as an example
}
String _getErrorMessage() {
Map<int, String> errors = {
1: 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).',
2: 'The file "%s" exceeds the upload limit defined in your form.',
3: 'The file "%s" was only partially uploaded.',
4: 'No file was uploaded.',
6: 'File could not be uploaded: missing temporary directory.',
7: 'The file "%s" could not be written on disk.',
8: 'File upload was stopped by a PHP extension.',
};
int maxFilesize = _error == 1 ? getMaxFilesize() ~/ 1024 : 0;
String message = errors[_error] ??
'The file "%s" was not uploaded due to an unknown error.';
return message
.replaceAll('%s', _originalName)
.replaceAll('%d', maxFilesize.toString());
}
String _getName(String name) {
return p.basename(name);
}
File _getTargetFile(String directory, String? name) {
name ??= p.basename(path);
return File(p.join(directory, name));
}
}

View file

@ -14,6 +14,7 @@ dependencies:
email_validator: ^3.0.0 email_validator: ^3.0.0
validator_dart: ^0.1.0 validator_dart: ^0.1.0
protevus_mime: ^0.0.1 protevus_mime: ^0.0.1
path: ^1.9.0
dev_dependencies: dev_dependencies:
lints: ^3.0.0 lints: ^3.0.0