blob: 248f266bce039a3ebe6f31bd3b806c1451a2fc07 [file] [log] [blame]
// Copyright (c) 2021, 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:http/http.dart' as http;
import 'request_impl.dart';
import 'requests.dart' as client_requests;
const contentTypeJsonUtf8 = 'application/json; charset=utf-8';
/// Does media uploads using the multipart upload protocol.
class MultipartMediaUploader {
static const _boundary = '314159265358979323846';
static const _base64Encoder = Base64Encoder();
final http.Client _httpClient;
final client_requests.Media _uploadMedia;
final Uri _uri;
final String _body;
final String _method;
final Map<String, String> _requestHeaders;
MultipartMediaUploader(
this._httpClient,
this._uploadMedia,
this._body,
this._uri,
this._method,
this._requestHeaders,
);
Future<http.StreamedResponse> upload() {
// NOTE: We assume that [_body] is encoded JSON without any \r or \n in it.
// This guarantees us that [_body] cannot contain a valid multipart
// boundary.
final bodyHead =
'${'--$_boundary\r\n'}${'Content-Type: $contentTypeJsonUtf8\r\n\r\n'}'
'$_body${'\r\n--$_boundary\r\n'}'
'${'Content-Type: ${_uploadMedia.contentType}\r\n'}'
'Content-Transfer-Encoding: base64\r\n\r\n';
const bodyTail = '\r\n--$_boundary--';
final headBytes = utf8.encode(bodyHead);
final totalLength = headBytes.length +
_lengthOfBase64Stream(_uploadMedia.length!) +
bodyTail.length;
final bodyController = StreamController<List<int>>()..add(headBytes);
Future.microtask(() async {
try {
await bodyController.addStream(
_uploadMedia.stream
.transform(_base64Encoder)
.transform(ascii.encoder),
);
bodyController.add(ascii.encode(bodyTail));
} catch (e, stack) {
bodyController.addError(e, stack);
} finally {
await bodyController.close();
}
});
final headers = {
..._requestHeaders,
'content-type': 'multipart/related; boundary="$_boundary"',
'content-length': '$totalLength'
};
final bodyStream = bodyController.stream;
final request =
RequestImpl(_method, _uri, stream: bodyStream, headers: headers);
return _httpClient.send(request);
}
}
int _lengthOfBase64Stream(int lengthOfByteStream) =>
((lengthOfByteStream + 2) ~/ 3) * 4;