LCOV - code coverage report
Current view: top level - http-0.11.3+15/lib/src - multipart_request.dart (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 0 48 0.0 %
Date: 2017-10-10 20:17:03 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
       2             : // for details. All rights reserved. Use of this source code is governed by a
       3             : // BSD-style license that can be found in the LICENSE file.
       4             : 
       5             : import 'dart:async';
       6             : import 'dart:convert';
       7             : import 'dart:math';
       8             : 
       9             : import 'base_request.dart';
      10             : import 'boundary_characters.dart';
      11             : import 'byte_stream.dart';
      12             : import 'multipart_file.dart';
      13             : import 'utils.dart';
      14             : 
      15             : final _newlineRegExp = new RegExp(r"\r\n|\r|\n");
      16             : 
      17             : /// A `multipart/form-data` request. Such a request has both string [fields],
      18             : /// which function as normal form fields, and (potentially streamed) binary
      19             : /// [files].
      20             : ///
      21             : /// This request automatically sets the Content-Type header to
      22             : /// `multipart/form-data`. This value will override any value set by the user.
      23             : ///
      24             : ///     var uri = Uri.parse("http://pub.dartlang.org/packages/create");
      25             : ///     var request = new http.MultipartRequest("POST", url);
      26             : ///     request.fields['user'] = 'nweiz@google.com';
      27             : ///     request.files.add(new http.MultipartFile.fromFile(
      28             : ///         'package',
      29             : ///         new File('build/package.tar.gz'),
      30             : ///         contentType: new MediaType('application', 'x-tar'));
      31             : ///     request.send().then((response) {
      32             : ///       if (response.statusCode == 200) print("Uploaded!");
      33             : ///     });
      34             : class MultipartRequest extends BaseRequest {
      35             :   /// The total length of the multipart boundaries used when building the
      36             :   /// request body. According to http://tools.ietf.org/html/rfc1341.html, this
      37             :   /// can't be longer than 70.
      38             :   static const int _BOUNDARY_LENGTH = 70;
      39             : 
      40             :   static final Random _random = new Random();
      41             : 
      42             :   /// The form fields to send for this request.
      43             :   final Map<String, String> fields;
      44             : 
      45             :   /// The private version of [files].
      46             :   final List<MultipartFile> _files;
      47             : 
      48             :   /// Creates a new [MultipartRequest].
      49             :   MultipartRequest(String method, Uri url)
      50           0 :     : fields = {},
      51           0 :       _files = <MultipartFile>[],
      52           0 :       super(method, url);
      53             : 
      54             :   /// The list of files to upload for this request.
      55           0 :   List<MultipartFile> get files => _files;
      56             : 
      57             :   /// The total length of the request body, in bytes. This is calculated from
      58             :   /// [fields] and [files] and cannot be set manually.
      59             :   int get contentLength {
      60             :     var length = 0;
      61             : 
      62           0 :     fields.forEach((name, value) {
      63           0 :       length += "--".length + _BOUNDARY_LENGTH + "\r\n".length +
      64           0 :           UTF8.encode(_headerForField(name, value)).length +
      65           0 :           UTF8.encode(value).length + "\r\n".length;
      66             :     });
      67             : 
      68           0 :     for (var file in _files) {
      69           0 :       length += "--".length + _BOUNDARY_LENGTH + "\r\n".length +
      70           0 :           UTF8.encode(_headerForFile(file)).length +
      71           0 :           file.length + "\r\n".length;
      72             :     }
      73             : 
      74           0 :     return length + "--".length + _BOUNDARY_LENGTH + "--\r\n".length;
      75             :   }
      76             : 
      77             :   void set contentLength(int value) {
      78           0 :     throw new UnsupportedError("Cannot set the contentLength property of "
      79             :         "multipart requests.");
      80             :   }
      81             : 
      82             :   /// Freezes all mutable fields and returns a single-subscription [ByteStream]
      83             :   /// that will emit the request body.
      84             :   ByteStream finalize() {
      85             :     // TODO(nweiz): freeze fields and files
      86           0 :     var boundary = _boundaryString();
      87           0 :     headers['content-type'] = 'multipart/form-data; boundary=$boundary';
      88           0 :     super.finalize();
      89             : 
      90           0 :     var controller = new StreamController<List<int>>(sync: true);
      91             : 
      92             :     void writeAscii(String string) {
      93           0 :       controller.add(UTF8.encode(string));
      94             :     }
      95             : 
      96           0 :     writeUtf8(String string) => controller.add(UTF8.encode(string));
      97           0 :     writeLine() => controller.add([13, 10]); // \r\n
      98             : 
      99           0 :     fields.forEach((name, value) {
     100           0 :       writeAscii('--$boundary\r\n');
     101           0 :       writeAscii(_headerForField(name, value));
     102           0 :       writeUtf8(value);
     103           0 :       writeLine();
     104             :     });
     105             : 
     106           0 :     Future.forEach(_files, (file) {
     107           0 :       writeAscii('--$boundary\r\n');
     108           0 :       writeAscii(_headerForFile(file));
     109           0 :       return writeStreamToSink(file.finalize(), controller)
     110           0 :         .then((_) => writeLine());
     111           0 :     }).then((_) {
     112             :       // TODO(nweiz): pass any errors propagated through this future on to
     113             :       // the stream. See issue 3657.
     114           0 :       writeAscii('--$boundary--\r\n');
     115           0 :       controller.close();
     116             :     });
     117             : 
     118           0 :     return new ByteStream(controller.stream);
     119             :   }
     120             : 
     121             :   /// Returns the header string for a field. The return value is guaranteed to
     122             :   /// contain only ASCII characters.
     123             :   String _headerForField(String name, String value) {
     124             :     var header =
     125           0 :         'content-disposition: form-data; name="${_browserEncode(name)}"';
     126           0 :     if (!isPlainAscii(value)) {
     127             :       header = '$header\r\n'
     128             :           'content-type: text/plain; charset=utf-8\r\n'
     129           0 :           'content-transfer-encoding: binary';
     130             :     }
     131           0 :     return '$header\r\n\r\n';
     132             :   }
     133             : 
     134             :   /// Returns the header string for a file. The return value is guaranteed to
     135             :   /// contain only ASCII characters.
     136             :   String _headerForFile(MultipartFile file) {
     137           0 :     var header = 'content-type: ${file.contentType}\r\n'
     138           0 :       'content-disposition: form-data; name="${_browserEncode(file.field)}"';
     139             : 
     140           0 :     if (file.filename != null) {
     141           0 :       header = '$header; filename="${_browserEncode(file.filename)}"';
     142             :     }
     143           0 :     return '$header\r\n\r\n';
     144             :   }
     145             : 
     146             :   /// Encode [value] in the same way browsers do.
     147             :   String _browserEncode(String value) {
     148             :     // http://tools.ietf.org/html/rfc2388 mandates some complex encodings for
     149             :     // field names and file names, but in practice user agents seem not to
     150             :     // follow this at all. Instead, they URL-encode `\r`, `\n`, and `\r\n` as
     151             :     // `\r\n`; URL-encode `"`; and do nothing else (even for `%` or non-ASCII
     152             :     // characters). We follow their behavior.
     153           0 :     return value.replaceAll(_newlineRegExp, "%0D%0A").replaceAll('"', "%22");
     154             :   }
     155             : 
     156             :   /// Returns a randomly-generated multipart boundary string
     157             :   String _boundaryString() {
     158             :     var prefix = "dart-http-boundary-";
     159           0 :     var list = new List<int>.generate(_BOUNDARY_LENGTH - prefix.length,
     160             :         (index) =>
     161           0 :             BOUNDARY_CHARACTERS[_random.nextInt(BOUNDARY_CHARACTERS.length)],
     162             :         growable: false);
     163           0 :     return "$prefix${new String.fromCharCodes(list)}";
     164             :   }
     165             : }

Generated by: LCOV version 1.13