5
5
import 'dart:async' ;
6
6
import 'dart:convert' ;
7
7
8
- import 'package:collection/collection.dart' ;
9
8
import 'package:http_parser/http_parser.dart' ;
10
9
11
10
import 'body.dart' ;
@@ -45,6 +44,9 @@ abstract class Message {
45
44
/// This can be read via [read] or [readAsString] .
46
45
final Body _body;
47
46
47
+ /// The parsed version of the Content-Type header in [headers] .
48
+ final MediaType _contentType;
49
+
48
50
/// Creates a new [Message] .
49
51
///
50
52
/// [body] is the message body. It may be either a [String] , a [List<int>] , a
@@ -61,14 +63,23 @@ abstract class Message {
61
63
{Encoding encoding,
62
64
Map <String , String > headers,
63
65
Map <String , Object > context})
64
- : this ._(new Body (body, encoding), headers, context);
66
+ : this .__(body, _determineMediaType (body, encoding, headers), headers,
67
+ context);
68
+
69
+ Message .__(body, MediaType contentType, Map <String , String > headers,
70
+ Map <String , Object > context)
71
+ : this ._(new Body (body, encodingForMediaType (contentType, null )),
72
+ contentType, headers, context);
65
73
66
- Message ._(Body body, Map <String , String > headers, Map <String , Object > context)
74
+ Message ._(Body body, MediaType contentType, Map <String , String > headers,
75
+ Map <String , Object > context)
67
76
: _body = body,
68
- headers = new HttpUnmodifiableMap <String >(_adjustHeaders (headers, body),
77
+ headers = new HttpUnmodifiableMap <String >(
78
+ _adjustHeaders (headers, body, contentType),
69
79
ignoreKeyCase: true ),
70
80
context =
71
- new HttpUnmodifiableMap <Object >(context, ignoreKeyCase: false );
81
+ new HttpUnmodifiableMap <Object >(context, ignoreKeyCase: false ),
82
+ _contentType = contentType;
72
83
73
84
/// If `true` , the stream returned by [read] won't emit any bytes.
74
85
///
@@ -84,6 +95,7 @@ abstract class Message {
84
95
_contentLengthCache = int .parse (headers['content-length' ]);
85
96
return _contentLengthCache;
86
97
}
98
+
87
99
int _contentLengthCache;
88
100
89
101
/// The MIME type declared in [headers] .
@@ -92,11 +104,7 @@ abstract class Message {
92
104
/// the MIME type, without any Content-Type parameters.
93
105
///
94
106
/// If [headers] doesn't have a Content-Type header, this will be `null` .
95
- String get mimeType {
96
- var contentType = _contentType;
97
- if (contentType == null ) return null ;
98
- return contentType.mimeType;
99
- }
107
+ String get mimeType => _contentType? .mimeType;
100
108
101
109
/// The encoding of the body returned by [read] .
102
110
///
@@ -105,23 +113,7 @@ abstract class Message {
105
113
///
106
114
/// If [headers] doesn't have a Content-Type header or it specifies an
107
115
/// encoding that [dart:convert] doesn't support, this will be `null` .
108
- Encoding get encoding {
109
- var contentType = _contentType;
110
- if (contentType == null ) return null ;
111
- if (! contentType.parameters.containsKey ('charset' )) return null ;
112
- return Encoding .getByName (contentType.parameters['charset' ]);
113
- }
114
-
115
- /// The parsed version of the Content-Type header in [headers] .
116
- ///
117
- /// This is cached for efficient access.
118
- MediaType get _contentType {
119
- if (_contentTypeCache != null ) return _contentTypeCache;
120
- if (! headers.containsKey ('content-type' )) return null ;
121
- _contentTypeCache = new MediaType .parse (headers['content-type' ]);
122
- return _contentTypeCache;
123
- }
124
- MediaType _contentTypeCache;
116
+ Encoding get encoding => _body.encoding;
125
117
126
118
/// Returns the message body as byte chunks.
127
119
///
@@ -144,55 +136,119 @@ abstract class Message {
144
136
/// changes.
145
137
Message change (
146
138
{Map <String , String > headers, Map <String , Object > context, body});
147
- }
148
139
149
- /// Adds information about encoding to [headers] .
150
- ///
151
- /// Returns a new map without modifying [headers] .
152
- Map <String , String > _adjustHeaders (Map <String , String > headers, Body body) {
153
- var sameEncoding = _sameEncoding (headers, body);
154
- if (sameEncoding) {
155
- if (body.contentLength == null ||
156
- getHeader (headers, 'content-length' ) == body.contentLength.toString ()) {
157
- return headers ?? const HttpUnmodifiableMap .empty ();
158
- } else if (body.contentLength == 0 &&
159
- (headers == null || headers.isEmpty)) {
160
- return const HttpUnmodifiableMap .empty ();
140
+ /// Determines the media type based on the [headers] , [encoding] and [body] .
141
+ static MediaType _determineMediaType (
142
+ body, Encoding encoding, Map <String , String > headers) =>
143
+ _headerMediaType (headers, encoding) ?? _defaultMediaType (body, encoding);
144
+
145
+ static MediaType _defaultMediaType (body, Encoding encoding) {
146
+ //if (body == null) return null;
147
+
148
+ var parameters = {'charset' : encoding? .name ?? UTF8 .name};
149
+
150
+ if (body is String ) {
151
+ return new MediaType ('text' , 'plain' , parameters);
152
+ } else if (body is Map ) {
153
+ return new MediaType ('application' , 'x-www-form-urlencoded' , parameters);
154
+ } else if (encoding != null ) {
155
+ return new MediaType ('application' , 'octet-stream' , parameters);
161
156
}
157
+
158
+ return null ;
162
159
}
163
160
164
- var newHeaders = headers == null
165
- ? new CaseInsensitiveMap <String >()
166
- : new CaseInsensitiveMap <String >.from (headers);
167
-
168
- if (! sameEncoding) {
169
- if (newHeaders['content-type' ] == null ) {
170
- newHeaders['content-type' ] =
171
- 'application/octet-stream; charset=${body .encoding .name }' ;
172
- } else {
173
- var contentType = new MediaType .parse (newHeaders['content-type' ])
174
- .change (parameters: {'charset' : body.encoding.name});
175
- newHeaders['content-type' ] = contentType.toString ();
176
- }
161
+ static MediaType _headerMediaType (
162
+ Map <String , String > headers, Encoding encoding) {
163
+ var contentTypeHeader = getHeader (headers, 'content-type' );
164
+ if (contentTypeHeader == null ) return null ;
165
+
166
+ var contentType = new MediaType .parse (contentTypeHeader);
167
+ var parameters = {
168
+ 'charset' :
169
+ encoding? .name ?? contentType.parameters['charset' ] ?? UTF8 .name
170
+ };
171
+
172
+ return contentType.change (parameters: parameters);
177
173
}
178
174
179
- if (body.contentLength != null ) {
180
- var coding = newHeaders['transfer-encoding' ];
181
- if (coding == null || equalsIgnoreAsciiCase (coding, 'identity' )) {
182
- newHeaders['content-length' ] = body.contentLength.toString ();
175
+ /// Adjusts the [headers] to include information from the [body] .
176
+ ///
177
+ /// Returns a new map without modifying [headers] .
178
+ ///
179
+ /// The following headers could be added or modified.
180
+ /// * content-length
181
+ /// * content-type
182
+ static Map <String , String > _adjustHeaders (
183
+ Map <String , String > headers, Body body, MediaType contentType) {
184
+ var modified = < String , String > {};
185
+
186
+ var contentLengthHeader = _adjustContentLengthHeader (headers, body);
187
+ if (contentLengthHeader.isNotEmpty) {
188
+ modified['content-length' ] = contentLengthHeader;
189
+ }
190
+
191
+ var contentTypeHeader = _adjustContentTypeHeader (headers, contentType);
192
+ if (contentTypeHeader.isNotEmpty) {
193
+ modified['content-type' ] = contentTypeHeader;
194
+ }
195
+
196
+ if (modified.isEmpty) {
197
+ return headers ?? const HttpUnmodifiableMap .empty ();
183
198
}
199
+
200
+ var newHeaders = headers == null
201
+ ? new CaseInsensitiveMap <String >()
202
+ : new CaseInsensitiveMap <String >.from (headers);
203
+
204
+ newHeaders.addAll (modified);
205
+
206
+ return newHeaders;
184
207
}
185
208
186
- return newHeaders;
187
- }
209
+ /// Checks the `content-length` header to see if it requires modification.
210
+ ///
211
+ /// Returns an empty string when no modification is required, otherwise it
212
+ /// returns the value to set.
213
+ ///
214
+ /// If there is a contentLength specified within the [body] and it does not
215
+ /// match what is specified in the [headers] it will be modified to the body's
216
+ /// value.
217
+ static String _adjustContentLengthHeader (
218
+ Map <String , String > headers, Body body) {
219
+ var bodyContentLength = body.contentLength ?? - 1 ;
220
+
221
+ if (bodyContentLength >= 0 ) {
222
+ var bodyContentHeader = bodyContentLength.toString ();
223
+
224
+ if (getHeader (headers, 'content-length' ) != bodyContentHeader) {
225
+ return bodyContentHeader;
226
+ }
227
+ }
228
+
229
+ return '' ;
230
+ }
188
231
189
- /// Returns whether [headers] declares the same encoding as [body] .
190
- bool _sameEncoding (Map <String , String > headers, Body body) {
191
- if (body.encoding == null ) return true ;
232
+ /// Checks the `content-type` header to see if it requires modification.
233
+ ///
234
+ /// Returns an empty string when no modification is required, otherwise it
235
+ /// returns the value to set.
236
+ ///
237
+ /// If the contentType within [body] is different than the one specified in the
238
+ /// [headers] then body's value will be used. The [headers] were already used
239
+ /// when creating the body's contentType so this will only actually change
240
+ /// things when headers did not contain a `content-type` .
241
+ static String _adjustContentTypeHeader (
242
+ Map <String , String > headers, MediaType contentType) {
243
+ var headerContentType = getHeader (headers, 'content-type' );
244
+ var bodyContentType = contentType? .toString ();
192
245
193
- var contentType = getHeader (headers, 'content-type' );
194
- if (contentType == null ) return false ;
246
+ // Neither are set so don't modify it
247
+ if ((headerContentType == null ) && (bodyContentType == null )) {
248
+ return '' ;
249
+ }
195
250
196
- var charset = new MediaType .parse (contentType).parameters['charset' ];
197
- return Encoding .getByName (charset) == body.encoding;
251
+ // The value of bodyContentType will have the overridden values so use that
252
+ return headerContentType != bodyContentType ? bodyContentType : '' ;
253
+ }
198
254
}
0 commit comments