blob: e4042411a9608ae49211645b6e1e20262aac1990 [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'package:async/async.dart';
import 'package:http_parser/http_parser.dart';
import 'package:mime/mime.dart';
/// A file to be uploaded as part of a `multipart/form-data` Request.
///
/// This doesn't need to correspond to a physical file.
class MultipartFile {
/// The stream that will emit the file's contents.
Stream<List<int>> _stream;
/// The name of the form field for the file.
final String field;
/// The size of the file in bytes.
///
/// This must be known in advance, even if this file is created from a
/// [Stream].
final int length;
/// The basename of the file. May be null.
final String filename;
/// The content-type of the file.
///
/// Defaults to `application/octet-stream`.
final MediaType contentType;
/// Creates a [MultipartFile] from the [value].
///
/// [value] can be either a [String] or a [List<int>].
///
/// For a String [value] the content will be encoded using [encoding] which
/// defaults to [utf8]. The `charset` from [contentType] is ignored when
/// encoding the String.
///
/// [contentType] if not specified will attempt to be looked up from the
/// bytes contained within the [stream] and the [filename] if provided. It
/// will default to `plain/text` for [String]s and `application/octet-stream`
/// for [List<int>].
factory MultipartFile(String field, value,
{String filename, MediaType contentType, Encoding encoding}) {
List<int> bytes;
var defaultMediaType;
if (value is String) {
encoding ??= utf8;
bytes = encoding.encode(value);
defaultMediaType = new MediaType('text', 'plain');
} else if (value is List<int>) {
bytes = value;
defaultMediaType = new MediaType('application', 'octet-stream');
} else {
throw new ArgumentError.value(
value, 'value', 'value must be either a String or a List<int>');
}
contentType ??= _lookUpMediaType(filename, bytes) ?? defaultMediaType;
if (encoding != null) {
contentType = contentType.change(parameters: {'charset': encoding.name});
}
return new MultipartFile.fromStream(
field, new Stream.fromIterable([bytes]), bytes.length,
filename: filename, contentType: contentType);
}
/// Creates a new [MultipartFile] from a chunked [stream] of bytes.
///
/// The [length] of the file in bytes must be known in advance. If it's not
/// then use [loadStream] to create the [MultipartFile] instance.
///
/// [contentType] if not specified will attempt to be looked up from the
/// [filename] if provided. It will default to `application/octet-stream`.
MultipartFile.fromStream(this.field, Stream<List<int>> stream, this.length,
{String filename, MediaType contentType})
: _stream = stream,
filename = filename,
contentType = contentType ??
_lookUpMediaType(filename) ??
new MediaType('application', 'octet-stream');
/// Creates a new [MultipartFile] from the [stream].
///
/// This method should be used when the length of [stream] in bytes is not
/// known ahead of time.
///
/// [contentType] if not specified will attempt to be looked up from the
/// bytes contained within the [stream] and the [filename] if provided. It
/// will default to `application/octet-stream`.
static Future<MultipartFile> loadStream(
String field, Stream<List<int>> stream,
{String filename, MediaType contentType}) async {
var bytes = await collectBytes(stream);
return new MultipartFile(field, bytes,
filename: filename, contentType: contentType);
}
/// Returns a [Stream] representing the contents of the file.
///
/// Can only be called once.
Stream<List<int>> read() {
if (_stream == null) {
throw new StateError('The "read" method can only be called once on a '
'http.MultipartFile object.');
}
var stream = _stream;
_stream = null;
return stream;
}
/// Looks up the [MediaType] from the [filename]'s extension or from
/// magic numbers contained within a file header's [bytes].
static MediaType _lookUpMediaType(String filename, [List<int> bytes]) {
if (filename == null && bytes == null) return null;
// lookupMimeType expects filename to be non-null but its possible that
// this can be called with bytes but no filename.
// FIXME: https://github.com/dart-lang/mime/issues/11
var mimeType = lookupMimeType(filename ?? '', headerBytes: bytes);
return mimeType != null ? new MediaType.parse(mimeType) : null;
}
}