[HOT-FIX] Download attachment is failed on iOS (#4405)

This commit is contained in:
Dat Vu
2026-03-24 22:16:43 +07:00
committed by GitHub
parent 6a34e27340
commit 8fff83a735
3 changed files with 130 additions and 6 deletions
@@ -277,7 +277,7 @@ class EmailAPI
return _downloadManager.downloadFile( return _downloadManager.downloadFile(
attachment.getDownloadUrl(baseDownloadUrl, accountId), attachment.getDownloadUrl(baseDownloadUrl, accountId),
getTemporaryDirectory(), getTemporaryDirectory(),
attachment.name ?? '', attachment.generateFileName(),
authentication, authentication,
cancelToken: cancelToken); cancelToken: cancelToken);
} }
+12 -5
View File
@@ -29,6 +29,7 @@ class Attachment with EquatableMixin {
static const String eventICSSubtype = 'ics'; static const String eventICSSubtype = 'ics';
static const String eventCalendarSubtype = 'calendar'; static const String eventCalendarSubtype = 'calendar';
static const String applicationRTFType = 'application/rtf'; static const String applicationRTFType = 'application/rtf';
static const String _defaultName = 'unknown-attachment';
final PartId? partId; final PartId? partId;
final Id? blobId; final Id? blobId;
@@ -72,11 +73,17 @@ class Attachment with EquatableMixin {
} }
String generateFileName() { String generateFileName() {
if (name?.isNotEmpty == true) { final rawName = (name?.trim().isNotEmpty == true)
return name!; ? name!.trim()
} else { : (blobId != null
return '${blobId?.value}.${type?.subtype}'; ? (type?.subtype != null
} ? '${blobId!.value}.${type!.subtype}'
: blobId!.value)
: _defaultName);
final sanitized = rawName.replaceAll(RegExp(r'[\\/:*?"<>|]'), '_').trim();
// Fall back if sanitized name has no meaningful content (e.g. '???' → '___')
return sanitized.contains(RegExp(r'[^_\s]')) ? sanitized : _defaultName;
} }
factory Attachment.fromJson(Map<String, dynamic> json) => _$AttachmentFromJson(json); factory Attachment.fromJson(Map<String, dynamic> json) => _$AttachmentFromJson(json);
+117
View File
@@ -63,5 +63,122 @@ void main() {
expect(result, 'http://localhost/download/1/some-blob-id/ti%C3%AAu%20%C4%91%E1%BB%81%20attachment?name=ti%C3%AAu%20%C4%91%E1%BB%81%20attachment&type=application%2Foctet-stream'); expect(result, 'http://localhost/download/1/some-blob-id/ti%C3%AAu%20%C4%91%E1%BB%81%20attachment?name=ti%C3%AAu%20%C4%91%E1%BB%81%20attachment&type=application%2Foctet-stream');
}); });
}); });
group('generateFileName:', () {
test(
'should return name when name is not empty',
() {
final attachment = Attachment(
blobId: Id('some-blob-id'),
name: 'document.pdf',
type: MediaType.parse('application/pdf'),
);
expect(attachment.generateFileName(), 'document.pdf');
});
test(
'should return name is trimmed when name has leading/trailing spaces',
() {
final attachment = Attachment(
blobId: Id('some-blob-id'),
name: ' document.pdf ',
type: MediaType.parse('application/pdf'),
);
expect(attachment.generateFileName(), 'document.pdf');
});
test(
'should return blobId with extension when name is null',
() {
final attachment = Attachment(
blobId: Id('some-blob-id'),
type: MediaType.parse('image/png'),
);
expect(attachment.generateFileName(), 'some-blob-id.png');
});
test(
'should return blobId with extension when name is empty',
() {
final attachment = Attachment(
blobId: Id('some-blob-id'),
name: '',
type: MediaType.parse('image/png'),
);
expect(attachment.generateFileName(), 'some-blob-id.png');
});
test(
'should return blobId with extension when name is only whitespace',
() {
final attachment = Attachment(
blobId: Id('some-blob-id'),
name: ' ',
type: MediaType.parse('image/jpeg'),
);
expect(attachment.generateFileName(), 'some-blob-id.jpeg');
});
test(
'should return blobId without extension when name is null and type is null',
() {
final attachment = Attachment(
blobId: Id('some-blob-id'),
);
expect(attachment.generateFileName(), 'some-blob-id');
});
test(
'should return default name when both name and blobId are null',
() {
final attachment = Attachment(
type: MediaType.parse('image/png'),
);
expect(attachment.generateFileName(), 'unknown-attachment');
});
test(
'should sanitize name with question mark character',
() {
final attachment = Attachment(
blobId: Id('some-blob-id'),
name: 'doc?.pdf',
type: MediaType.parse('application/pdf'),
);
expect(attachment.generateFileName(), 'doc_.pdf');
});
test(
'should sanitize name with forward slash character',
() {
final attachment = Attachment(
blobId: Id('some-blob-id'),
name: 'folder/document.pdf',
type: MediaType.parse('application/pdf'),
);
expect(attachment.generateFileName(), 'folder_document.pdf');
});
test(
'should return default name when name consists entirely of illegal characters',
() {
final attachment = Attachment(
blobId: Id('some-blob-id'),
name: '???',
type: MediaType.parse('application/pdf'),
);
expect(attachment.generateFileName(), 'unknown-attachment');
});
});
}); });
} }