|
| 1 | +import 'dart:math'; |
| 2 | + |
1 | 3 | import 'package:app_settings/app_settings.dart'; |
2 | 4 | import 'package:flutter/material.dart'; |
3 | 5 | import 'package:flutter/services.dart'; |
4 | 6 | import 'package:flutter_gen/gen_l10n/zulip_localizations.dart'; |
| 7 | +import 'package:mime/mime.dart'; |
5 | 8 |
|
6 | 9 | import '../api/exception.dart'; |
7 | 10 | import '../api/model/model.dart'; |
@@ -421,11 +424,17 @@ class _FixedDestinationContentInput extends StatelessWidget { |
421 | 424 | /// A convenience class to represent data from the generic file picker, |
422 | 425 | /// the media library, and the camera, in a single form. |
423 | 426 | class _File { |
424 | | - _File({required this.content, required this.length, required this.filename}); |
| 427 | + _File({ |
| 428 | + required this.content, |
| 429 | + required this.length, |
| 430 | + required this.filename, |
| 431 | + required this.mimeType, |
| 432 | + }); |
425 | 433 |
|
426 | 434 | final Stream<List<int>> content; |
427 | 435 | final int length; |
428 | 436 | final String filename; |
| 437 | + final String? mimeType; |
429 | 438 | } |
430 | 439 |
|
431 | 440 | Future<void> _uploadFiles({ |
@@ -472,14 +481,14 @@ Future<void> _uploadFiles({ |
472 | 481 | } |
473 | 482 |
|
474 | 483 | for (final (tag, file) in uploadsInProgress) { |
475 | | - final _File(:content, :length, :filename) = file; |
| 484 | + final _File(:content, :length, :filename, :mimeType) = file; |
476 | 485 | Uri? url; |
477 | 486 | try { |
478 | 487 | final result = await uploadFile(store.connection, |
479 | 488 | content: content, |
480 | 489 | length: length, |
481 | 490 | filename: filename, |
482 | | - contentType: null, // TODO(#829) |
| 491 | + contentType: mimeType, |
483 | 492 | ); |
484 | 493 | url = Uri.parse(result.uri); |
485 | 494 | } catch (e) { |
@@ -577,7 +586,22 @@ Future<Iterable<_File>> _getFilePickerFiles(BuildContext context, FileType type) |
577 | 586 |
|
578 | 587 | return result.files.map((f) { |
579 | 588 | assert(f.readStream != null); // We passed `withReadStream: true` to pickFiles. |
580 | | - return _File(content: f.readStream!, length: f.size, filename: f.name); |
| 589 | + final mimeType = lookupMimeType( |
| 590 | + // Seems like the path shouldn't be required; we still want to look for |
| 591 | + // matches on `headerBytes`. Thankfully we can still do that, by calling |
| 592 | + // lookupMimeType with the empty string as the path. That's a value that |
| 593 | + // doesn't map to any particular type, so the path will be effectively |
| 594 | + // ignored, as desired. Upstream comment: |
| 595 | + // https://github.com/dart-lang/mime/issues/11#issuecomment-2246824452 |
| 596 | + f.path ?? '', |
| 597 | + headerBytes: f.bytes?.take(defaultMagicNumbersMaxLength).toList(), |
| 598 | + ); |
| 599 | + return _File( |
| 600 | + content: f.readStream!, |
| 601 | + length: f.size, |
| 602 | + filename: f.name, |
| 603 | + mimeType: mimeType, |
| 604 | + ); |
581 | 605 | }); |
582 | 606 | } |
583 | 607 |
|
@@ -662,7 +686,27 @@ class _AttachFromCameraButton extends _AttachUploadsButton { |
662 | 686 | } |
663 | 687 | final length = await result.length(); |
664 | 688 |
|
665 | | - return [_File(content: result.openRead(), length: length, filename: result.name)]; |
| 689 | + List<int>? headerBytes; |
| 690 | + try { |
| 691 | + headerBytes = await result.openRead( |
| 692 | + 0, |
| 693 | + // Despite its dartdoc, [XFile.openRead] can throw if `end` is greater |
| 694 | + // than the file's length. We can *probably* trust our `length` to be |
| 695 | + // accurate, but it's nontrivial to verify. If it's inaccurate, we'd |
| 696 | + // rather sacrifice this part of the MIME lookup than throw the whole |
| 697 | + // upload. So, the try/catch. |
| 698 | + min(defaultMagicNumbersMaxLength, length) |
| 699 | + ).expand((l) => l).toList(); |
| 700 | + } catch (e) { |
| 701 | + // TODO(log) |
| 702 | + } |
| 703 | + return [_File( |
| 704 | + content: result.openRead(), |
| 705 | + length: length, |
| 706 | + filename: result.name, |
| 707 | + mimeType: result.mimeType |
| 708 | + ?? lookupMimeType(result.path, headerBytes: headerBytes), |
| 709 | + )]; |
666 | 710 | } |
667 | 711 | } |
668 | 712 |
|
|
0 commit comments