update: updating ports from the symfony http foundation component
This commit is contained in:
parent
830fcdf0da
commit
0c7bc94cc3
12 changed files with 315 additions and 10 deletions
|
@ -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';
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
|
@ -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.
|
|
||||||
}
|
}
|
||||||
|
|
174
packages/http/lib/src/foundation/file/file.dart
Normal file
174
packages/http/lib/src/foundation/file/file.dart
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue