OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of dart.io; | 5 part of dart.io; |
6 | 6 |
7 class _HttpHeaders implements HttpHeaders { | 7 class _HttpHeaders implements HttpHeaders { |
| 8 final Map<String, List<String>> _headers; |
| 9 final String protocolVersion; |
| 10 |
| 11 bool _mutable = true; // Are the headers currently mutable? |
| 12 List<String> _noFoldingHeaders; |
| 13 |
| 14 int _contentLength = -1; |
| 15 bool _persistentConnection = true; |
| 16 bool _chunkedTransferEncoding = false; |
| 17 String _host; |
| 18 int _port; |
| 19 |
8 _HttpHeaders(String this.protocolVersion) | 20 _HttpHeaders(String this.protocolVersion) |
9 : _headers = new HashMap<String, List<String>>(); | 21 : _headers = new HashMap<String, List<String>>() { |
| 22 if (protocolVersion == "1.0") { |
| 23 _persistentConnection = false; |
| 24 } |
| 25 } |
10 | 26 |
11 List<String> operator[](String name) { | 27 List<String> operator[](String name) { |
12 name = name.toLowerCase(); | 28 name = name.toLowerCase(); |
13 return _headers[name]; | 29 return _headers[name]; |
14 } | 30 } |
15 | 31 |
16 String value(String name) { | 32 String value(String name) { |
17 name = name.toLowerCase(); | 33 name = name.toLowerCase(); |
18 List<String> values = _headers[name]; | 34 List<String> values = _headers[name]; |
19 if (values == null) return null; | 35 if (values == null) return null; |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
63 | 79 |
64 void forEach(void f(String name, List<String> values)) { | 80 void forEach(void f(String name, List<String> values)) { |
65 _headers.forEach(f); | 81 _headers.forEach(f); |
66 } | 82 } |
67 | 83 |
68 void noFolding(String name) { | 84 void noFolding(String name) { |
69 if (_noFoldingHeaders == null) _noFoldingHeaders = new List<String>(); | 85 if (_noFoldingHeaders == null) _noFoldingHeaders = new List<String>(); |
70 _noFoldingHeaders.add(name); | 86 _noFoldingHeaders.add(name); |
71 } | 87 } |
72 | 88 |
73 bool get persistentConnection { | 89 bool get persistentConnection => _persistentConnection; |
74 List<String> connection = _headers[HttpHeaders.CONNECTION]; | |
75 if (protocolVersion == "1.1") { | |
76 if (connection == null) return true; | |
77 return !connection.any((value) => value.toLowerCase() == "close"); | |
78 } else { | |
79 if (connection == null) return false; | |
80 return connection.any((value) => value.toLowerCase() == "keep-alive"); | |
81 } | |
82 } | |
83 | 90 |
84 void set persistentConnection(bool persistentConnection) { | 91 void set persistentConnection(bool persistentConnection) { |
85 _checkMutable(); | 92 _checkMutable(); |
86 // Determine the value of the "Connection" header. | 93 if (persistentConnection == _persistentConnection) return; |
87 remove(HttpHeaders.CONNECTION, "close"); | 94 if (persistentConnection) { |
88 remove(HttpHeaders.CONNECTION, "keep-alive"); | 95 if (protocolVersion == "1.1") { |
89 if (protocolVersion == "1.1" && !persistentConnection) { | 96 remove(HttpHeaders.CONNECTION, "close"); |
90 add(HttpHeaders.CONNECTION, "close"); | 97 } else { |
91 } else if (protocolVersion == "1.0" && persistentConnection) { | 98 if (_contentLength == -1) { |
92 add(HttpHeaders.CONNECTION, "keep-alive"); | 99 throw new HttpException( |
| 100 "Trying to set 'Connection: Keep-Alive' on HTTP 1.0 headers with " |
| 101 "no ContentLength"); |
| 102 } |
| 103 add(HttpHeaders.CONNECTION, "keep-alive"); |
| 104 } |
| 105 } else { |
| 106 if (protocolVersion == "1.1") { |
| 107 add(HttpHeaders.CONNECTION, "close"); |
| 108 } else { |
| 109 remove(HttpHeaders.CONNECTION, "keep-alive"); |
| 110 } |
93 } | 111 } |
| 112 _persistentConnection = persistentConnection; |
94 } | 113 } |
95 | 114 |
96 int get contentLength => _contentLength; | 115 int get contentLength => _contentLength; |
97 | 116 |
98 void set contentLength(int contentLength) { | 117 void set contentLength(int contentLength) { |
99 _checkMutable(); | 118 _checkMutable(); |
| 119 if (protocolVersion == "1.0" && |
| 120 persistentConnection && |
| 121 contentLength == -1) { |
| 122 throw new HttpException( |
| 123 "Trying to clear ContentLength on HTTP 1.0 headers with " |
| 124 "'Connection: Keep-Alive' set"); |
| 125 } |
100 _contentLength = contentLength; | 126 _contentLength = contentLength; |
101 if (_contentLength >= 0) { | 127 if (_contentLength >= 0) { |
| 128 if (chunkedTransferEncoding) chunkedTransferEncoding = false; |
102 _set(HttpHeaders.CONTENT_LENGTH, contentLength.toString()); | 129 _set(HttpHeaders.CONTENT_LENGTH, contentLength.toString()); |
103 } else { | 130 } else { |
104 removeAll(HttpHeaders.CONTENT_LENGTH); | 131 removeAll(HttpHeaders.CONTENT_LENGTH); |
105 } | 132 } |
106 } | 133 } |
107 | 134 |
108 bool get chunkedTransferEncoding => _chunkedTransferEncoding; | 135 bool get chunkedTransferEncoding => _chunkedTransferEncoding; |
109 | 136 |
110 void set chunkedTransferEncoding(bool chunkedTransferEncoding) { | 137 void set chunkedTransferEncoding(bool chunkedTransferEncoding) { |
111 _checkMutable(); | 138 _checkMutable(); |
112 _chunkedTransferEncoding = chunkedTransferEncoding; | 139 if (chunkedTransferEncoding && protocolVersion == "1.0") { |
113 List<String> values = _headers[HttpHeaders.TRANSFER_ENCODING]; | 140 throw new HttpException( |
114 if ((values == null || values[values.length - 1] != "chunked") && | 141 "Trying to set 'Transfer-Encoding: Chunked' on HTTP 1.0 headers"); |
115 chunkedTransferEncoding) { | 142 } |
116 // Headers does not specify chunked encoding - add it if set. | 143 if (chunkedTransferEncoding == _chunkedTransferEncoding) return; |
| 144 if (chunkedTransferEncoding) { |
| 145 List<String> values = _headers[HttpHeaders.TRANSFER_ENCODING]; |
| 146 if ((values == null || values.last != "chunked")) { |
| 147 // Headers does not specify chunked encoding - add it if set. |
117 _addValue(HttpHeaders.TRANSFER_ENCODING, "chunked"); | 148 _addValue(HttpHeaders.TRANSFER_ENCODING, "chunked"); |
118 } else if (!chunkedTransferEncoding) { | 149 } |
| 150 contentLength = -1; |
| 151 } else { |
119 // Headers does specify chunked encoding - remove it if not set. | 152 // Headers does specify chunked encoding - remove it if not set. |
120 remove(HttpHeaders.TRANSFER_ENCODING, "chunked"); | 153 remove(HttpHeaders.TRANSFER_ENCODING, "chunked"); |
121 } | 154 } |
| 155 _chunkedTransferEncoding = chunkedTransferEncoding; |
122 } | 156 } |
123 | 157 |
124 String get host => _host; | 158 String get host => _host; |
125 | 159 |
126 void set host(String host) { | 160 void set host(String host) { |
127 _checkMutable(); | 161 _checkMutable(); |
128 _host = host; | 162 _host = host; |
129 _updateHostHeader(); | 163 _updateHostHeader(); |
130 } | 164 } |
131 | 165 |
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
268 _port = int.parse(value.substring(pos + 1)); | 302 _port = int.parse(value.substring(pos + 1)); |
269 } on FormatException catch (e) { | 303 } on FormatException catch (e) { |
270 _port = null; | 304 _port = null; |
271 } | 305 } |
272 } | 306 } |
273 } | 307 } |
274 _set(HttpHeaders.HOST, value); | 308 _set(HttpHeaders.HOST, value); |
275 } else { | 309 } else { |
276 throw new HttpException("Unexpected type for header named $name"); | 310 throw new HttpException("Unexpected type for header named $name"); |
277 } | 311 } |
| 312 } else if (name == HttpHeaders.CONNECTION) { |
| 313 var lowerCaseValue = value.toLowerCase(); |
| 314 if (lowerCaseValue == 'close') { |
| 315 _persistentConnection = false; |
| 316 } else if (lowerCaseValue == 'keep-alive') { |
| 317 _persistentConnection = true; |
| 318 } |
| 319 _addValue(name, value); |
278 } else if (name == HttpHeaders.CONTENT_TYPE) { | 320 } else if (name == HttpHeaders.CONTENT_TYPE) { |
279 _set(HttpHeaders.CONTENT_TYPE, value); | 321 _set(HttpHeaders.CONTENT_TYPE, value); |
280 } else { | 322 } else { |
281 _addValue(name, value); | 323 _addValue(name, value); |
282 } | 324 } |
283 } | 325 } |
284 | 326 |
285 void _addValue(String name, Object value) { | 327 void _addValue(String name, Object value) { |
286 List<String> values = _headers[name]; | 328 List<String> values = _headers[name]; |
287 if (values == null) { | 329 if (values == null) { |
(...skipping 26 matching lines...) Expand all Loading... |
314 | 356 |
315 _foldHeader(String name) { | 357 _foldHeader(String name) { |
316 if (name == HttpHeaders.SET_COOKIE || | 358 if (name == HttpHeaders.SET_COOKIE || |
317 (_noFoldingHeaders != null && | 359 (_noFoldingHeaders != null && |
318 _noFoldingHeaders.indexOf(name) != -1)) { | 360 _noFoldingHeaders.indexOf(name) != -1)) { |
319 return false; | 361 return false; |
320 } | 362 } |
321 return true; | 363 return true; |
322 } | 364 } |
323 | 365 |
324 void _synchronize() { | |
325 // If the content length is not known make sure chunked transfer | |
326 // encoding is used for HTTP 1.1. | |
327 if (contentLength < 0) { | |
328 if (protocolVersion == "1.0") { | |
329 persistentConnection = false; | |
330 } else { | |
331 chunkedTransferEncoding = true; | |
332 } | |
333 } | |
334 // If a Transfer-Encoding header field is present the | |
335 // Content-Length header MUST NOT be sent (RFC 2616 section 4.4). | |
336 if (chunkedTransferEncoding && | |
337 contentLength >= 0 && | |
338 protocolVersion == "1.1") { | |
339 contentLength = -1; | |
340 } | |
341 } | |
342 | |
343 void _finalize() { | 366 void _finalize() { |
344 _synchronize(); | |
345 _mutable = false; | 367 _mutable = false; |
346 } | 368 } |
347 | 369 |
348 int _write(Uint8List buffer, int offset) { | 370 int _write(Uint8List buffer, int offset) { |
349 void write(List<int> bytes) { | 371 void write(List<int> bytes) { |
350 int len = bytes.length; | 372 int len = bytes.length; |
351 for (int i = 0; i < len; i++) { | 373 for (int i = 0; i < len; i++) { |
352 buffer[offset + i] = bytes[i]; | 374 buffer[offset + i] = bytes[i]; |
353 } | 375 } |
354 offset += len; | 376 offset += len; |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
456 if (done()) return; | 478 if (done()) return; |
457 expect(";"); | 479 expect(";"); |
458 } | 480 } |
459 } | 481 } |
460 List<String> values = _headers[HttpHeaders.COOKIE]; | 482 List<String> values = _headers[HttpHeaders.COOKIE]; |
461 if (values != null) { | 483 if (values != null) { |
462 values.forEach((headerValue) => parseCookieString(headerValue)); | 484 values.forEach((headerValue) => parseCookieString(headerValue)); |
463 } | 485 } |
464 return cookies; | 486 return cookies; |
465 } | 487 } |
466 | |
467 | |
468 bool _mutable = true; // Are the headers currently mutable? | |
469 Map<String, List<String>> _headers; | |
470 List<String> _noFoldingHeaders; | |
471 | |
472 int _contentLength = -1; | |
473 bool _chunkedTransferEncoding = false; | |
474 final String protocolVersion; | |
475 String _host; | |
476 int _port; | |
477 } | 488 } |
478 | 489 |
479 | 490 |
480 class _HeaderValue implements HeaderValue { | 491 class _HeaderValue implements HeaderValue { |
481 String _value; | 492 String _value; |
482 _UnmodifiableMap<String, String> _parameters; | 493 _UnmodifiableMap<String, String> _parameters; |
483 | 494 |
484 _HeaderValue([String this._value = "", Map<String, String> parameters]) { | 495 _HeaderValue([String this._value = "", Map<String, String> parameters]) { |
485 if (parameters != null) { | 496 if (parameters != null) { |
486 _parameters = | 497 _parameters = |
(...skipping 341 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
828 void clear() { | 839 void clear() { |
829 throw new UnsupportedError("Cannot modify an unmodifiable map"); | 840 throw new UnsupportedError("Cannot modify an unmodifiable map"); |
830 } | 841 } |
831 void forEach(void f(K key, V value)) => _map.forEach(f); | 842 void forEach(void f(K key, V value)) => _map.forEach(f); |
832 Iterable<K> get keys => _map.keys; | 843 Iterable<K> get keys => _map.keys; |
833 Iterable<V> get values => _map.values; | 844 Iterable<V> get values => _map.values; |
834 int get length => _map.length; | 845 int get length => _map.length; |
835 bool get isEmpty => _map.isEmpty; | 846 bool get isEmpty => _map.isEmpty; |
836 bool get isNotEmpty => _map.isNotEmpty; | 847 bool get isNotEmpty => _map.isNotEmpty; |
837 } | 848 } |
OLD | NEW |