LCOV - code coverage report
Current view: top level - Users/duwen/Documents/code/dio/dio/lib/src - form_data.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 73 74 98.6 %
Date: 2021-11-28 14:37:50 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:async';
       2             : import 'dart:convert';
       3             : import 'dart:math';
       4             : 
       5             : import 'multipart_file.dart';
       6             : import 'options.dart';
       7             : import 'utils.dart';
       8             : 
       9             : /// A class to create readable "multipart/form-data" streams.
      10             : /// It can be used to submit forms and file uploads to http server.
      11             : class FormData {
      12             :   static const String _BOUNDARY_PRE_TAG = '--dio-boundary-';
      13             :   static const _BOUNDARY_LENGTH = _BOUNDARY_PRE_TAG.length + 10;
      14             : 
      15             :   late String _boundary;
      16             : 
      17             :   /// The boundary of FormData, it consists of a constant prefix and a random
      18             :   /// postfix to assure the the boundary unpredictable and unique, each FormData
      19             :   /// instance will be different.
      20           2 :   String get boundary => _boundary;
      21             : 
      22             :   final _newlineRegExp = RegExp(r'\r\n|\r|\n');
      23             : 
      24             :   /// The form fields to send for this request.
      25             :   final fields = <MapEntry<String, String>>[];
      26             : 
      27             :   /// The [files].
      28             :   final files = <MapEntry<String, MultipartFile>>[];
      29             : 
      30             :   /// Whether [finalize] has been called.
      31           2 :   bool get isFinalized => _isFinalized;
      32             :   bool _isFinalized = false;
      33             : 
      34           1 :   FormData() {
      35           1 :     _init();
      36             :   }
      37             : 
      38             :   /// Create FormData instance with a Map.
      39           1 :   FormData.fromMap(
      40             :     Map<String, dynamic> map, [
      41             :     ListFormat collectionFormat = ListFormat.multi,
      42             :   ]) {
      43           1 :     _init();
      44           1 :     encodeMap(
      45             :       map,
      46           1 :       (key, value) {
      47             :         if (value == null) return null;
      48           1 :         if (value is MultipartFile) {
      49           3 :           files.add(MapEntry(key, value));
      50             :         } else {
      51           4 :           fields.add(MapEntry(key, value.toString()));
      52             :         }
      53             :         return null;
      54             :       },
      55             :       listFormat: collectionFormat,
      56             :       encode: false,
      57             :     );
      58             :   }
      59             : 
      60           1 :   void _init() {
      61             :     // Assure the boundary unpredictable and unique
      62           1 :     var random = Random();
      63           2 :     _boundary = _BOUNDARY_PRE_TAG +
      64           3 :         random.nextInt(4294967296).toString().padLeft(10, '0');
      65             :   }
      66             : 
      67             :   /// Returns the header string for a field. The return value is guaranteed to
      68             :   /// contain only ASCII characters.
      69           1 :   String _headerForField(String name, String value) {
      70             :     var header =
      71           2 :         'content-disposition: form-data; name="${_browserEncode(name)}"';
      72           1 :     if (!isPlainAscii(value)) {
      73           0 :       header = '$header\r\n'
      74             :           'content-type: text/plain; charset=utf-8\r\n'
      75             :           'content-transfer-encoding: binary';
      76             :     }
      77           1 :     return '$header\r\n\r\n';
      78             :   }
      79             : 
      80             :   /// Returns the header string for a file. The return value is guaranteed to
      81             :   /// contain only ASCII characters.
      82           1 :   String _headerForFile(MapEntry<String, MultipartFile> entry) {
      83           1 :     var file = entry.value;
      84             :     var header =
      85           3 :         'content-disposition: form-data; name="${_browserEncode(entry.key)}"';
      86           1 :     if (file.filename != null) {
      87           3 :       header = '$header; filename="${_browserEncode(file.filename)}"';
      88             :     }
      89           1 :     header = '$header\r\n'
      90           1 :         'content-type: ${file.contentType}';
      91           1 :     if (file.headers != null) {
      92             :       // append additional headers
      93           3 :       file.headers!.forEach((key, values) {
      94           2 :         values.forEach((value) {
      95           1 :           header = '$header\r\n'
      96             :               '$key: $value';
      97             :         });
      98             :       });
      99             :     }
     100           1 :     return '$header\r\n\r\n';
     101             :   }
     102             : 
     103             :   /// Encode [value] in the same way browsers do.
     104           1 :   String? _browserEncode(String? value) {
     105             :     // http://tools.ietf.org/html/rfc2388 mandates some complex encodings for
     106             :     // field names and file names, but in practice user agents seem not to
     107             :     // follow this at all. Instead, they URL-encode `\r`, `\n`, and `\r\n` as
     108             :     // `\r\n`; URL-encode `"`; and do nothing else (even for `%` or non-ASCII
     109             :     // characters). We follow their behavior.
     110             :     if (value == null) {
     111             :       return null;
     112             :     }
     113           3 :     return value.replaceAll(_newlineRegExp, '%0D%0A').replaceAll('"', '%22');
     114             :   }
     115             : 
     116             :   /// The total length of the request body, in bytes. This is calculated from
     117             :   /// [fields] and [files] and cannot be set manually.
     118           1 :   int get length {
     119             :     var length = 0;
     120           3 :     fields.forEach((entry) {
     121           3 :       length += '--'.length +
     122           1 :           _BOUNDARY_LENGTH +
     123           2 :           '\r\n'.length +
     124           6 :           utf8.encode(_headerForField(entry.key, entry.value)).length +
     125           4 :           utf8.encode(entry.value).length +
     126           1 :           '\r\n'.length;
     127             :     });
     128             : 
     129           2 :     for (var file in files) {
     130           3 :       length += '--'.length +
     131           1 :           _BOUNDARY_LENGTH +
     132           2 :           '\r\n'.length +
     133           4 :           utf8.encode(_headerForFile(file)).length +
     134           3 :           file.value.length +
     135           1 :           '\r\n'.length;
     136             :     }
     137             : 
     138           5 :     return length + '--'.length + _BOUNDARY_LENGTH + '--\r\n'.length;
     139             :   }
     140             : 
     141           1 :   Stream<List<int>> finalize() {
     142           1 :     if (isFinalized) {
     143           1 :       throw StateError("Can't finalize a finalized MultipartFile.");
     144             :     }
     145           1 :     _isFinalized = true;
     146           1 :     var controller = StreamController<List<int>>(sync: false);
     147           1 :     void writeAscii(String string) {
     148           2 :       controller.add(utf8.encode(string));
     149             :     }
     150             : 
     151           3 :     void writeUtf8(String string) => controller.add(utf8.encode(string));
     152           3 :     void writeLine() => controller.add([13, 10]); // \r\n
     153             : 
     154           3 :     fields.forEach((entry) {
     155           2 :       writeAscii('--$boundary\r\n');
     156           3 :       writeAscii(_headerForField(entry.key, entry.value));
     157           1 :       writeUtf8(entry.value);
     158             :       writeLine();
     159             :     });
     160             : 
     161           3 :     Future.forEach<MapEntry<String, MultipartFile>>(files, (file) {
     162           2 :       writeAscii('--$boundary\r\n');
     163           1 :       writeAscii(_headerForFile(file));
     164           3 :       return writeStreamToSink(file.value.finalize(), controller)
     165           2 :           .then((_) => writeLine());
     166           2 :     }).then((_) {
     167           2 :       writeAscii('--$boundary--\r\n');
     168           1 :       controller.close();
     169             :     });
     170           1 :     return controller.stream;
     171             :   }
     172             : 
     173             :   ///Transform the entire FormData contents as a list of bytes asynchronously.
     174           1 :   Future<List<int>> readAsBytes() {
     175           7 :     return Future(() => finalize().reduce((a, b) => [...a, ...b]));
     176             :   }
     177             : }

Generated by: LCOV version 1.14