LCOV - code coverage report
Current view: top level - src - form_data.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 69 69 100.0 %
Date: 2020-02-27 17:47:50 Functions: 0 0 -

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

Generated by: LCOV version 1.14