readFiles function

Future<List<Map<String, dynamic>>?> readFiles(
  1. H4Event event, {
  2. required String fieldName,
  3. String? customFilePath,
  4. bool hashFileName = false,
  5. int maxFileSize = 10,
})

Reads file(s) from a multipart/form-data request for a specific field name.

Example usage:

final files = await readFiles(event,
  fieldName: 'photos',
  hashFileName: true,
  maxFileSize: 5 // 5MB limit
);

Parameters:

  • fieldName: The form field name containing the file(s)
  • customFilePath: Optional custom path to save uploaded files
  • hashFileName: If true, generates a unique hashed filename (default: false)
  • maxFileSize: Maximum file size in MB (default: 10MB)

Returns a List of file information maps containing:

{
  'path': String,         // File path on disk
  'mimeType': String,     // File content type
  'originalname': String, // Original file name
  'fieldName': String,    // Form field name
  'size': int,           // File size in bytes
  'filename': String // current file name
}

Returns null if no files are found for the specified fieldName.

Throws CreateError if:

  • Request is not multipart/form-data
  • Boundary is missing in content type
  • File size exceeds maxFileSize
  • File upload fails

Implementation

Future<List<Map<String, dynamic>>?> readFiles(H4Event event,
    {required String fieldName,
    String? customFilePath,
    bool hashFileName = false,
    int maxFileSize = 10}) async {
  final HttpRequest request = event.node["value"]!;
  final contentType = request.headers.contentType;

  if (contentType?.mimeType != 'multipart/form-data') {
    throw CreateError(
        message: 'Files can only be uploaded using multipart/form-data');
  }

  final boundary = contentType!.parameters['boundary'];
  if (boundary == null) {
    throw CreateError(message: 'Missing boundary in multipart/form-data');
  }

  final mimeTransformer = MimeMultipartTransformer(boundary);
  final parts = request.cast<List<int>>().transform(mimeTransformer);
  List<Map<String, dynamic>> files = [];

  try {
    await for (final part in parts) {
      final headers = part.headers;
      final contentType = headers['content-type'];
      final contentDisposition = headers['content-disposition'];
      final nameMatch =
          RegExp(r'name="([^"]*)"').firstMatch(contentDisposition ?? '');
      final currentFieldName = nameMatch?.group(1);
      final filenameMatch =
          RegExp(r'filename="([^"]*)"').firstMatch(contentDisposition ?? '');
      final filename = filenameMatch?.group(1);

      // Only process if it matches the requested fieldName and has a filename
      if (currentFieldName == fieldName && filename != null) {
        final fileInfo = await _streamToStorage(part,
            originalFilename: filename,
            customFilePath: customFilePath,
            hashFileName: hashFileName,
            maxFileSize: maxFileSize);
        files.add({
          'path': fileInfo['path'],
          'mimeType': contentType,
          'originalname': filename,
          'fieldName': fieldName,
          'size': fileInfo['size'],
          'filename': fileInfo['filename'],
        });
      }
    }
    return files.isEmpty ? null : files;
  } catch (e) {
    throw CreateError(
        message: 'Failed to read files from formdata: ${e.toString()}');
  }
}