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:convert';
6 : import 'dart:typed_data';
7 :
8 : import 'package:http_parser/http_parser.dart';
9 :
10 : import 'base_request.dart';
11 : import 'byte_stream.dart';
12 : import 'utils.dart';
13 :
14 : /// An HTTP request where the entire request body is known in advance.
15 : class Request extends BaseRequest {
16 : /// The size of the request body, in bytes. This is calculated from
17 : /// [bodyBytes].
18 : ///
19 : /// The content length cannot be set for [Request], since it's automatically
20 : /// calculated from [bodyBytes].
21 0 : int get contentLength => bodyBytes.length;
22 :
23 : set contentLength(int value) {
24 0 : throw new UnsupportedError("Cannot set the contentLength property of "
25 : "non-streaming Request objects.");
26 : }
27 :
28 : /// The default encoding to use when converting between [bodyBytes] and
29 : /// [body]. This is only used if [encoding] hasn't been manually set and if
30 : /// the content-type header has no encoding information.
31 : Encoding _defaultEncoding;
32 :
33 : /// The encoding used for the request. This encoding is used when converting
34 : /// between [bodyBytes] and [body].
35 : ///
36 : /// If the request has a `Content-Type` header and that header has a `charset`
37 : /// parameter, that parameter's value is used as the encoding. Otherwise, if
38 : /// [encoding] has been set manually, that encoding is used. If that hasn't
39 : /// been set either, this defaults to [UTF8].
40 : ///
41 : /// If the `charset` parameter's value is not a known [Encoding], reading this
42 : /// will throw a [FormatException].
43 : ///
44 : /// If the request has a `Content-Type` header, setting this will set the
45 : /// charset parameter on that header.
46 : Encoding get encoding {
47 0 : if (_contentType == null ||
48 0 : !_contentType.parameters.containsKey('charset')) {
49 0 : return _defaultEncoding;
50 : }
51 0 : return requiredEncodingForCharset(_contentType.parameters['charset']);
52 : }
53 :
54 : set encoding(Encoding value) {
55 0 : _checkFinalized();
56 0 : _defaultEncoding = value;
57 0 : var contentType = _contentType;
58 : if (contentType == null) return;
59 0 : _contentType = contentType.change(parameters: {'charset': value.name});
60 : }
61 :
62 : // TODO(nweiz): make this return a read-only view
63 : /// The bytes comprising the body of the request. This is converted to and
64 : /// from [body] using [encoding].
65 : ///
66 : /// This list should only be set, not be modified in place.
67 0 : Uint8List get bodyBytes => _bodyBytes;
68 : Uint8List _bodyBytes;
69 :
70 : set bodyBytes(List<int> value) {
71 0 : _checkFinalized();
72 0 : _bodyBytes = toUint8List(value);
73 : }
74 :
75 : /// The body of the request as a string. This is converted to and from
76 : /// [bodyBytes] using [encoding].
77 : ///
78 : /// When this is set, if the request does not yet have a `Content-Type`
79 : /// header, one will be added with the type `text/plain`. Then the `charset`
80 : /// parameter of the `Content-Type` header (whether new or pre-existing) will
81 : /// be set to [encoding] if it wasn't already set.
82 0 : String get body => encoding.decode(bodyBytes);
83 :
84 : set body(String value) {
85 0 : bodyBytes = encoding.encode(value);
86 0 : var contentType = _contentType;
87 : if (contentType == null) {
88 0 : _contentType = new MediaType("text", "plain", {'charset': encoding.name});
89 0 : } else if (!contentType.parameters.containsKey('charset')) {
90 0 : _contentType = contentType.change(parameters: {'charset': encoding.name});
91 : }
92 : }
93 :
94 : /// The form-encoded fields in the body of the request as a map from field
95 : /// names to values. The form-encoded body is converted to and from
96 : /// [bodyBytes] using [encoding] (in the same way as [body]).
97 : ///
98 : /// If the request doesn't have a `Content-Type` header of
99 : /// `application/x-www-form-urlencoded`, reading this will throw a
100 : /// [StateError].
101 : ///
102 : /// If the request has a `Content-Type` header with a type other than
103 : /// `application/x-www-form-urlencoded`, setting this will throw a
104 : /// [StateError]. Otherwise, the content type will be set to
105 : /// `application/x-www-form-urlencoded`.
106 : ///
107 : /// This map should only be set, not modified in place.
108 : Map<String, String> get bodyFields {
109 0 : var contentType = _contentType;
110 : if (contentType == null ||
111 0 : contentType.mimeType != "application/x-www-form-urlencoded") {
112 0 : throw new StateError('Cannot access the body fields of a Request without '
113 : 'content-type "application/x-www-form-urlencoded".');
114 : }
115 :
116 0 : return Uri.splitQueryString(body, encoding: encoding);
117 : }
118 :
119 : set bodyFields(Map<String, String> fields) {
120 0 : var contentType = _contentType;
121 : if (contentType == null) {
122 0 : _contentType = new MediaType("application", "x-www-form-urlencoded");
123 0 : } else if (contentType.mimeType != "application/x-www-form-urlencoded") {
124 0 : throw new StateError('Cannot set the body fields of a Request with '
125 0 : 'content-type "${contentType.mimeType}".');
126 : }
127 :
128 0 : this.body = mapToQuery(fields, encoding: encoding);
129 : }
130 :
131 : /// Creates a new HTTP request.
132 : Request(String method, Uri url)
133 : : _defaultEncoding = UTF8,
134 0 : _bodyBytes = new Uint8List(0),
135 0 : super(method, url);
136 :
137 : /// Freezes all mutable fields and returns a single-subscription [ByteStream]
138 : /// containing the request body.
139 : ByteStream finalize() {
140 0 : super.finalize();
141 0 : return new ByteStream.fromBytes(bodyBytes);
142 : }
143 :
144 : /// The `Content-Type` header of the request (if it exists) as a
145 : /// [MediaType].
146 : MediaType get _contentType {
147 0 : var contentType = headers['content-type'];
148 : if (contentType == null) return null;
149 0 : return new MediaType.parse(contentType);
150 : }
151 :
152 : set _contentType(MediaType value) {
153 0 : headers['content-type'] = value.toString();
154 : }
155 :
156 : /// Throw an error if this request has been finalized.
157 : void _checkFinalized() {
158 0 : if (!finalized) return;
159 0 : throw new StateError("Can't modify a finalized Request.");
160 : }
161 : }
|