Index: tool/input_sdk/lib/io/http_headers.dart |
diff --git a/tool/input_sdk/lib/io/http_headers.dart b/tool/input_sdk/lib/io/http_headers.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..6b14ae2d6c89208388f3ed676f8f12b537e3de49 |
--- /dev/null |
+++ b/tool/input_sdk/lib/io/http_headers.dart |
@@ -0,0 +1,999 @@ |
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+part of dart.io; |
+ |
+class _HttpHeaders implements HttpHeaders { |
+ final Map<String, List<String>> _headers; |
+ final String protocolVersion; |
+ |
+ bool _mutable = true; // Are the headers currently mutable? |
+ List<String> _noFoldingHeaders; |
+ |
+ int _contentLength = -1; |
+ bool _persistentConnection = true; |
+ bool _chunkedTransferEncoding = false; |
+ String _host; |
+ int _port; |
+ |
+ final int _defaultPortForScheme; |
+ |
+ _HttpHeaders(this.protocolVersion, |
+ {int defaultPortForScheme: HttpClient.DEFAULT_HTTP_PORT, |
+ _HttpHeaders initialHeaders}) |
+ : _headers = new HashMap<String, List<String>>(), |
+ _defaultPortForScheme = defaultPortForScheme { |
+ if (initialHeaders != null) { |
+ initialHeaders._headers.forEach((name, value) => _headers[name] = value); |
+ _contentLength = initialHeaders._contentLength; |
+ _persistentConnection = initialHeaders._persistentConnection; |
+ _chunkedTransferEncoding = initialHeaders._chunkedTransferEncoding; |
+ _host = initialHeaders._host; |
+ _port = initialHeaders._port; |
+ } |
+ if (protocolVersion == "1.0") { |
+ _persistentConnection = false; |
+ _chunkedTransferEncoding = false; |
+ } |
+ } |
+ |
+ List<String> operator[](String name) => _headers[name.toLowerCase()]; |
+ |
+ String value(String name) { |
+ name = name.toLowerCase(); |
+ List<String> values = _headers[name]; |
+ if (values == null) return null; |
+ if (values.length > 1) { |
+ throw new HttpException("More than one value for header $name"); |
+ } |
+ return values[0]; |
+ } |
+ |
+ void add(String name, value) { |
+ _checkMutable(); |
+ _addAll(_validateField(name), value); |
+ } |
+ |
+ void _addAll(String name, value) { |
+ assert(name == _validateField(name)); |
+ if (value is Iterable) { |
+ for (var v in value) { |
+ _add(name, _validateValue(v)); |
+ } |
+ } else { |
+ _add(name, _validateValue(value)); |
+ } |
+ } |
+ |
+ void set(String name, Object value) { |
+ _checkMutable(); |
+ name = _validateField(name); |
+ _headers.remove(name); |
+ if (name == HttpHeaders.TRANSFER_ENCODING) { |
+ _chunkedTransferEncoding = false; |
+ } |
+ _addAll(name, value); |
+ } |
+ |
+ void remove(String name, Object value) { |
+ _checkMutable(); |
+ name = _validateField(name); |
+ value = _validateValue(value); |
+ List<String> values = _headers[name]; |
+ if (values != null) { |
+ int index = values.indexOf(value); |
+ if (index != -1) { |
+ values.removeRange(index, index + 1); |
+ } |
+ if (values.length == 0) _headers.remove(name); |
+ } |
+ if (name == HttpHeaders.TRANSFER_ENCODING && value == "chunked") { |
+ _chunkedTransferEncoding = false; |
+ } |
+ } |
+ |
+ void removeAll(String name) { |
+ _checkMutable(); |
+ name = _validateField(name); |
+ _headers.remove(name); |
+ } |
+ |
+ void forEach(void f(String name, List<String> values)) { |
+ _headers.forEach(f); |
+ } |
+ |
+ void noFolding(String name) { |
+ if (_noFoldingHeaders == null) _noFoldingHeaders = new List<String>(); |
+ _noFoldingHeaders.add(name); |
+ } |
+ |
+ bool get persistentConnection => _persistentConnection; |
+ |
+ void set persistentConnection(bool persistentConnection) { |
+ _checkMutable(); |
+ if (persistentConnection == _persistentConnection) return; |
+ if (persistentConnection) { |
+ if (protocolVersion == "1.1") { |
+ remove(HttpHeaders.CONNECTION, "close"); |
+ } else { |
+ if (_contentLength == -1) { |
+ throw new HttpException( |
+ "Trying to set 'Connection: Keep-Alive' on HTTP 1.0 headers with " |
+ "no ContentLength"); |
+ } |
+ add(HttpHeaders.CONNECTION, "keep-alive"); |
+ } |
+ } else { |
+ if (protocolVersion == "1.1") { |
+ add(HttpHeaders.CONNECTION, "close"); |
+ } else { |
+ remove(HttpHeaders.CONNECTION, "keep-alive"); |
+ } |
+ } |
+ _persistentConnection = persistentConnection; |
+ } |
+ |
+ int get contentLength => _contentLength; |
+ |
+ void set contentLength(int contentLength) { |
+ _checkMutable(); |
+ if (protocolVersion == "1.0" && |
+ persistentConnection && |
+ contentLength == -1) { |
+ throw new HttpException( |
+ "Trying to clear ContentLength on HTTP 1.0 headers with " |
+ "'Connection: Keep-Alive' set"); |
+ } |
+ if (_contentLength == contentLength) return; |
+ _contentLength = contentLength; |
+ if (_contentLength >= 0) { |
+ if (chunkedTransferEncoding) chunkedTransferEncoding = false; |
+ _set(HttpHeaders.CONTENT_LENGTH, contentLength.toString()); |
+ } else { |
+ removeAll(HttpHeaders.CONTENT_LENGTH); |
+ if (protocolVersion == "1.1") { |
+ chunkedTransferEncoding = true; |
+ } |
+ } |
+ } |
+ |
+ bool get chunkedTransferEncoding => _chunkedTransferEncoding; |
+ |
+ void set chunkedTransferEncoding(bool chunkedTransferEncoding) { |
+ _checkMutable(); |
+ if (chunkedTransferEncoding && protocolVersion == "1.0") { |
+ throw new HttpException( |
+ "Trying to set 'Transfer-Encoding: Chunked' on HTTP 1.0 headers"); |
+ } |
+ if (chunkedTransferEncoding == _chunkedTransferEncoding) return; |
+ if (chunkedTransferEncoding) { |
+ List<String> values = _headers[HttpHeaders.TRANSFER_ENCODING]; |
+ if ((values == null || values.last != "chunked")) { |
+ // Headers does not specify chunked encoding - add it if set. |
+ _addValue(HttpHeaders.TRANSFER_ENCODING, "chunked"); |
+ } |
+ contentLength = -1; |
+ } else { |
+ // Headers does specify chunked encoding - remove it if not set. |
+ remove(HttpHeaders.TRANSFER_ENCODING, "chunked"); |
+ } |
+ _chunkedTransferEncoding = chunkedTransferEncoding; |
+ } |
+ |
+ String get host => _host; |
+ |
+ void set host(String host) { |
+ _checkMutable(); |
+ _host = host; |
+ _updateHostHeader(); |
+ } |
+ |
+ int get port => _port; |
+ |
+ void set port(int port) { |
+ _checkMutable(); |
+ _port = port; |
+ _updateHostHeader(); |
+ } |
+ |
+ DateTime get ifModifiedSince { |
+ List<String> values = _headers[HttpHeaders.IF_MODIFIED_SINCE]; |
+ if (values != null) { |
+ try { |
+ return HttpDate.parse(values[0]); |
+ } on Exception catch (e) { |
+ return null; |
+ } |
+ } |
+ return null; |
+ } |
+ |
+ void set ifModifiedSince(DateTime ifModifiedSince) { |
+ _checkMutable(); |
+ // Format "ifModifiedSince" header with date in Greenwich Mean Time (GMT). |
+ String formatted = HttpDate.format(ifModifiedSince.toUtc()); |
+ _set(HttpHeaders.IF_MODIFIED_SINCE, formatted); |
+ } |
+ |
+ DateTime get date { |
+ List<String> values = _headers[HttpHeaders.DATE]; |
+ if (values != null) { |
+ try { |
+ return HttpDate.parse(values[0]); |
+ } on Exception catch (e) { |
+ return null; |
+ } |
+ } |
+ return null; |
+ } |
+ |
+ void set date(DateTime date) { |
+ _checkMutable(); |
+ // Format "DateTime" header with date in Greenwich Mean Time (GMT). |
+ String formatted = HttpDate.format(date.toUtc()); |
+ _set("date", formatted); |
+ } |
+ |
+ DateTime get expires { |
+ List<String> values = _headers[HttpHeaders.EXPIRES]; |
+ if (values != null) { |
+ try { |
+ return HttpDate.parse(values[0]); |
+ } on Exception catch (e) { |
+ return null; |
+ } |
+ } |
+ return null; |
+ } |
+ |
+ void set expires(DateTime expires) { |
+ _checkMutable(); |
+ // Format "Expires" header with date in Greenwich Mean Time (GMT). |
+ String formatted = HttpDate.format(expires.toUtc()); |
+ _set(HttpHeaders.EXPIRES, formatted); |
+ } |
+ |
+ ContentType get contentType { |
+ var values = _headers["content-type"]; |
+ if (values != null) { |
+ return ContentType.parse(values[0]); |
+ } else { |
+ return null; |
+ } |
+ } |
+ |
+ void set contentType(ContentType contentType) { |
+ _checkMutable(); |
+ _set(HttpHeaders.CONTENT_TYPE, contentType.toString()); |
+ } |
+ |
+ void clear() { |
+ _checkMutable(); |
+ _headers.clear(); |
+ _contentLength = -1; |
+ _persistentConnection = true; |
+ _chunkedTransferEncoding = false; |
+ _host = null; |
+ _port = null; |
+ } |
+ |
+ // [name] must be a lower-case version of the name. |
+ void _add(String name, value) { |
+ assert(name == _validateField(name)); |
+ // Use the length as index on what method to call. This is notable |
+ // faster than computing hash and looking up in a hash-map. |
+ switch (name.length) { |
+ case 4: |
+ if (HttpHeaders.DATE == name) { |
+ _addDate(name, value); |
+ return; |
+ } |
+ if (HttpHeaders.HOST == name) { |
+ _addHost(name, value); |
+ return; |
+ } |
+ break; |
+ case 7: |
+ if (HttpHeaders.EXPIRES == name) { |
+ _addExpires(name, value); |
+ return; |
+ } |
+ break; |
+ case 10: |
+ if (HttpHeaders.CONNECTION == name) { |
+ _addConnection(name, value); |
+ return; |
+ } |
+ break; |
+ case 12: |
+ if (HttpHeaders.CONTENT_TYPE == name) { |
+ _addContentType(name, value); |
+ return; |
+ } |
+ break; |
+ case 14: |
+ if (HttpHeaders.CONTENT_LENGTH == name) { |
+ _addContentLength(name, value); |
+ return; |
+ } |
+ break; |
+ case 17: |
+ if (HttpHeaders.TRANSFER_ENCODING == name) { |
+ _addTransferEncoding(name, value); |
+ return; |
+ } |
+ if (HttpHeaders.IF_MODIFIED_SINCE == name) { |
+ _addIfModifiedSince(name, value); |
+ return; |
+ } |
+ } |
+ _addValue(name, value); |
+ } |
+ |
+ void _addContentLength(String name, value) { |
+ if (value is int) { |
+ contentLength = value; |
+ } else if (value is String) { |
+ contentLength = int.parse(value); |
+ } else { |
+ throw new HttpException("Unexpected type for header named $name"); |
+ } |
+ } |
+ |
+ void _addTransferEncoding(String name, value) { |
+ if (value == "chunked") { |
+ chunkedTransferEncoding = true; |
+ } else { |
+ _addValue(HttpHeaders.TRANSFER_ENCODING, value); |
+ } |
+ } |
+ |
+ void _addDate(String name, value) { |
+ if (value is DateTime) { |
+ date = value; |
+ } else if (value is String) { |
+ _set(HttpHeaders.DATE, value); |
+ } else { |
+ throw new HttpException("Unexpected type for header named $name"); |
+ } |
+ } |
+ |
+ void _addExpires(String name, value) { |
+ if (value is DateTime) { |
+ expires = value; |
+ } else if (value is String) { |
+ _set(HttpHeaders.EXPIRES, value); |
+ } else { |
+ throw new HttpException("Unexpected type for header named $name"); |
+ } |
+ } |
+ |
+ void _addIfModifiedSince(String name, value) { |
+ if (value is DateTime) { |
+ ifModifiedSince = value; |
+ } else if (value is String) { |
+ _set(HttpHeaders.IF_MODIFIED_SINCE, value); |
+ } else { |
+ throw new HttpException("Unexpected type for header named $name"); |
+ } |
+ } |
+ |
+ void _addHost(String name, value) { |
+ if (value is String) { |
+ int pos = value.indexOf(":"); |
+ if (pos == -1) { |
+ _host = value; |
+ _port = HttpClient.DEFAULT_HTTP_PORT; |
+ } else { |
+ if (pos > 0) { |
+ _host = value.substring(0, pos); |
+ } else { |
+ _host = null; |
+ } |
+ if (pos + 1 == value.length) { |
+ _port = HttpClient.DEFAULT_HTTP_PORT; |
+ } else { |
+ try { |
+ _port = int.parse(value.substring(pos + 1)); |
+ } on FormatException catch (e) { |
+ _port = null; |
+ } |
+ } |
+ } |
+ _set(HttpHeaders.HOST, value); |
+ } else { |
+ throw new HttpException("Unexpected type for header named $name"); |
+ } |
+ } |
+ |
+ void _addConnection(String name, value) { |
+ var lowerCaseValue = value.toLowerCase(); |
+ if (lowerCaseValue == 'close') { |
+ _persistentConnection = false; |
+ } else if (lowerCaseValue == 'keep-alive') { |
+ _persistentConnection = true; |
+ } |
+ _addValue(name, value); |
+ } |
+ |
+ void _addContentType(String name, value) { |
+ _set(HttpHeaders.CONTENT_TYPE, value); |
+ } |
+ |
+ void _addValue(String name, Object value) { |
+ List<String> values = _headers[name]; |
+ if (values == null) { |
+ values = new List<String>(); |
+ _headers[name] = values; |
+ } |
+ if (value is DateTime) { |
+ values.add(HttpDate.format(value)); |
+ } else if (value is String) { |
+ values.add(value); |
+ } else { |
+ values.add(_validateValue(value.toString())); |
+ } |
+ } |
+ |
+ void _set(String name, String value) { |
+ assert(name == _validateField(name)); |
+ List<String> values = new List<String>(); |
+ _headers[name] = values; |
+ values.add(value); |
+ } |
+ |
+ _checkMutable() { |
+ if (!_mutable) throw new HttpException("HTTP headers are not mutable"); |
+ } |
+ |
+ _updateHostHeader() { |
+ bool defaultPort = _port == null || _port == _defaultPortForScheme; |
+ _set("host", defaultPort ? host : "$host:$_port"); |
+ } |
+ |
+ _foldHeader(String name) { |
+ if (name == HttpHeaders.SET_COOKIE || |
+ (_noFoldingHeaders != null && |
+ _noFoldingHeaders.indexOf(name) != -1)) { |
+ return false; |
+ } |
+ return true; |
+ } |
+ |
+ void _finalize() { |
+ _mutable = false; |
+ } |
+ |
+ int _write(Uint8List buffer, int offset) { |
+ void write(List<int> bytes) { |
+ int len = bytes.length; |
+ for (int i = 0; i < len; i++) { |
+ buffer[offset + i] = bytes[i]; |
+ } |
+ offset += len; |
+ } |
+ |
+ // Format headers. |
+ for (String name in _headers.keys) { |
+ List<String> values = _headers[name]; |
+ bool fold = _foldHeader(name); |
+ var nameData = name.codeUnits; |
+ write(nameData); |
+ buffer[offset++] = _CharCode.COLON; |
+ buffer[offset++] = _CharCode.SP; |
+ for (int i = 0; i < values.length; i++) { |
+ if (i > 0) { |
+ if (fold) { |
+ buffer[offset++] = _CharCode.COMMA; |
+ buffer[offset++] = _CharCode.SP; |
+ } else { |
+ buffer[offset++] = _CharCode.CR; |
+ buffer[offset++] = _CharCode.LF; |
+ write(nameData); |
+ buffer[offset++] = _CharCode.COLON; |
+ buffer[offset++] = _CharCode.SP; |
+ } |
+ } |
+ write(values[i].codeUnits); |
+ } |
+ buffer[offset++] = _CharCode.CR; |
+ buffer[offset++] = _CharCode.LF; |
+ } |
+ return offset; |
+ } |
+ |
+ String toString() { |
+ StringBuffer sb = new StringBuffer(); |
+ _headers.forEach((String name, List<String> values) { |
+ sb..write(name)..write(": "); |
+ bool fold = _foldHeader(name); |
+ for (int i = 0; i < values.length; i++) { |
+ if (i > 0) { |
+ if (fold) { |
+ sb.write(", "); |
+ } else { |
+ sb..write("\n")..write(name)..write(": "); |
+ } |
+ } |
+ sb.write(values[i]); |
+ } |
+ sb.write("\n"); |
+ }); |
+ return sb.toString(); |
+ } |
+ |
+ List<Cookie> _parseCookies() { |
+ // Parse a Cookie header value according to the rules in RFC 6265. |
+ var cookies = new List<Cookie>(); |
+ void parseCookieString(String s) { |
+ int index = 0; |
+ |
+ bool done() => index == -1 || index == s.length; |
+ |
+ void skipWS() { |
+ while (!done()) { |
+ if (s[index] != " " && s[index] != "\t") return; |
+ index++; |
+ } |
+ } |
+ |
+ String parseName() { |
+ int start = index; |
+ while (!done()) { |
+ if (s[index] == " " || s[index] == "\t" || s[index] == "=") break; |
+ index++; |
+ } |
+ return s.substring(start, index); |
+ } |
+ |
+ String parseValue() { |
+ int start = index; |
+ while (!done()) { |
+ if (s[index] == " " || s[index] == "\t" || s[index] == ";") break; |
+ index++; |
+ } |
+ return s.substring(start, index); |
+ } |
+ |
+ bool expect(String expected) { |
+ if (done()) return false; |
+ if (s[index] != expected) return false; |
+ index++; |
+ return true; |
+ } |
+ |
+ while (!done()) { |
+ skipWS(); |
+ if (done()) return; |
+ String name = parseName(); |
+ skipWS(); |
+ if (!expect("=")) { |
+ index = s.indexOf(';', index); |
+ continue; |
+ } |
+ skipWS(); |
+ String value = parseValue(); |
+ try { |
+ cookies.add(new _Cookie(name, value)); |
+ } catch (_) { |
+ // Skip it, invalid cookie data. |
+ } |
+ skipWS(); |
+ if (done()) return; |
+ if (!expect(";")) { |
+ index = s.indexOf(';', index); |
+ continue; |
+ } |
+ } |
+ } |
+ List<String> values = _headers[HttpHeaders.COOKIE]; |
+ if (values != null) { |
+ values.forEach((headerValue) => parseCookieString(headerValue)); |
+ } |
+ return cookies; |
+ } |
+ |
+ static String _validateField(String field) { |
+ for (var i = 0; i < field.length; i++) { |
+ if (!_HttpParser._isTokenChar(field.codeUnitAt(i))) { |
+ throw new FormatException( |
+ "Invalid HTTP header field name: ${JSON.encode(field)}"); |
+ } |
+ } |
+ return field.toLowerCase(); |
+ } |
+ |
+ static _validateValue(value) { |
+ if (value is! String) return value; |
+ for (var i = 0; i < value.length; i++) { |
+ if (!_HttpParser._isValueChar(value.codeUnitAt(i))) { |
+ throw new FormatException( |
+ "Invalid HTTP header field value: ${JSON.encode(value)}"); |
+ } |
+ } |
+ return value; |
+ } |
+} |
+ |
+ |
+class _HeaderValue implements HeaderValue { |
+ String _value; |
+ Map<String, String> _parameters; |
+ Map<String, String> _unmodifiableParameters; |
+ |
+ _HeaderValue([String this._value = "", Map<String, String> parameters]) { |
+ if (parameters != null) { |
+ _parameters = new HashMap<String, String>.from(parameters); |
+ } |
+ } |
+ |
+ static _HeaderValue parse(String value, |
+ {parameterSeparator: ";", |
+ valueSeparator: null, |
+ preserveBackslash: false}) { |
+ // Parse the string. |
+ var result = new _HeaderValue(); |
+ result._parse(value, parameterSeparator, valueSeparator, preserveBackslash); |
+ return result; |
+ } |
+ |
+ String get value => _value; |
+ |
+ void _ensureParameters() { |
+ if (_parameters == null) { |
+ _parameters = new HashMap<String, String>(); |
+ } |
+ } |
+ |
+ Map<String, String> get parameters { |
+ _ensureParameters(); |
+ if (_unmodifiableParameters == null) { |
+ _unmodifiableParameters = new UnmodifiableMapView(_parameters); |
+ } |
+ return _unmodifiableParameters; |
+ } |
+ |
+ String toString() { |
+ StringBuffer sb = new StringBuffer(); |
+ sb.write(_value); |
+ if (parameters != null && parameters.length > 0) { |
+ _parameters.forEach((String name, String value) { |
+ sb..write("; ")..write(name)..write("=")..write(value); |
+ }); |
+ } |
+ return sb.toString(); |
+ } |
+ |
+ void _parse(String s, |
+ String parameterSeparator, |
+ String valueSeparator, |
+ bool preserveBackslash) { |
+ int index = 0; |
+ |
+ bool done() => index == s.length; |
+ |
+ void skipWS() { |
+ while (!done()) { |
+ if (s[index] != " " && s[index] != "\t") return; |
+ index++; |
+ } |
+ } |
+ |
+ String parseValue() { |
+ int start = index; |
+ while (!done()) { |
+ if (s[index] == " " || |
+ s[index] == "\t" || |
+ s[index] == valueSeparator || |
+ s[index] == parameterSeparator) break; |
+ index++; |
+ } |
+ return s.substring(start, index); |
+ } |
+ |
+ void expect(String expected) { |
+ if (done() || s[index] != expected) { |
+ throw new HttpException("Failed to parse header value"); |
+ } |
+ index++; |
+ } |
+ |
+ void maybeExpect(String expected) { |
+ if (s[index] == expected) index++; |
+ } |
+ |
+ void parseParameters() { |
+ var parameters = new HashMap<String, String>(); |
+ _parameters = new UnmodifiableMapView(parameters); |
+ |
+ String parseParameterName() { |
+ int start = index; |
+ while (!done()) { |
+ if (s[index] == " " || |
+ s[index] == "\t" || |
+ s[index] == "=" || |
+ s[index] == parameterSeparator || |
+ s[index] == valueSeparator) break; |
+ index++; |
+ } |
+ return s.substring(start, index).toLowerCase(); |
+ } |
+ |
+ String parseParameterValue() { |
+ if (!done() && s[index] == "\"") { |
+ // Parse quoted value. |
+ StringBuffer sb = new StringBuffer(); |
+ index++; |
+ while (!done()) { |
+ if (s[index] == "\\") { |
+ if (index + 1 == s.length) { |
+ throw new HttpException("Failed to parse header value"); |
+ } |
+ if (preserveBackslash && s[index + 1] != "\"") { |
+ sb.write(s[index]); |
+ } |
+ index++; |
+ } else if (s[index] == "\"") { |
+ index++; |
+ break; |
+ } |
+ sb.write(s[index]); |
+ index++; |
+ } |
+ return sb.toString(); |
+ } else { |
+ // Parse non-quoted value. |
+ var val = parseValue(); |
+ return val == "" ? null : val; |
+ } |
+ } |
+ |
+ while (!done()) { |
+ skipWS(); |
+ if (done()) return; |
+ String name = parseParameterName(); |
+ skipWS(); |
+ if (done()) { |
+ parameters[name] = null; |
+ return; |
+ } |
+ maybeExpect("="); |
+ skipWS(); |
+ if(done()) { |
+ parameters[name] = null; |
+ return; |
+ } |
+ String value = parseParameterValue(); |
+ if (name == 'charset' && this is _ContentType) { |
+ // Charset parameter of ContentTypes are always lower-case. |
+ value = value.toLowerCase(); |
+ } |
+ parameters[name] = value; |
+ skipWS(); |
+ if (done()) return; |
+ // TODO: Implement support for multi-valued parameters. |
+ if(s[index] == valueSeparator) return; |
+ expect(parameterSeparator); |
+ } |
+ } |
+ |
+ skipWS(); |
+ _value = parseValue(); |
+ skipWS(); |
+ if (done()) return; |
+ maybeExpect(parameterSeparator); |
+ parseParameters(); |
+ } |
+} |
+ |
+ |
+class _ContentType extends _HeaderValue implements ContentType { |
+ String _primaryType = ""; |
+ String _subType = ""; |
+ |
+ _ContentType(String primaryType, |
+ String subType, |
+ String charset, |
+ Map<String, String> parameters) |
+ : _primaryType = primaryType, _subType = subType, super("") { |
+ if (_primaryType == null) _primaryType = ""; |
+ if (_subType == null) _subType = ""; |
+ _value = "$_primaryType/$_subType"; |
+ if (parameters != null) { |
+ _ensureParameters(); |
+ parameters.forEach((String key, String value) { |
+ String lowerCaseKey = key.toLowerCase(); |
+ if (lowerCaseKey == "charset") { |
+ value = value.toLowerCase(); |
+ } |
+ this._parameters[lowerCaseKey] = value; |
+ }); |
+ } |
+ if (charset != null) { |
+ _ensureParameters(); |
+ this._parameters["charset"] = charset.toLowerCase(); |
+ } |
+ } |
+ |
+ _ContentType._(); |
+ |
+ static _ContentType parse(String value) { |
+ var result = new _ContentType._(); |
+ result._parse(value, ";", null, false); |
+ int index = result._value.indexOf("/"); |
+ if (index == -1 || index == (result._value.length - 1)) { |
+ result._primaryType = result._value.trim().toLowerCase(); |
+ result._subType = ""; |
+ } else { |
+ result._primaryType = |
+ result._value.substring(0, index).trim().toLowerCase(); |
+ result._subType = result._value.substring(index + 1).trim().toLowerCase(); |
+ } |
+ return result; |
+ } |
+ |
+ String get mimeType => '$primaryType/$subType'; |
+ |
+ String get primaryType => _primaryType; |
+ |
+ String get subType => _subType; |
+ |
+ String get charset => parameters["charset"]; |
+} |
+ |
+ |
+class _Cookie implements Cookie { |
+ String name; |
+ String value; |
+ DateTime expires; |
+ int maxAge; |
+ String domain; |
+ String path; |
+ bool httpOnly = false; |
+ bool secure = false; |
+ |
+ _Cookie([this.name, this.value]) { |
+ // Default value of httponly is true. |
+ httpOnly = true; |
+ _validate(); |
+ } |
+ |
+ _Cookie.fromSetCookieValue(String value) { |
+ // Parse the 'set-cookie' header value. |
+ _parseSetCookieValue(value); |
+ } |
+ |
+ // Parse a 'set-cookie' header value according to the rules in RFC 6265. |
+ void _parseSetCookieValue(String s) { |
+ int index = 0; |
+ |
+ bool done() => index == s.length; |
+ |
+ String parseName() { |
+ int start = index; |
+ while (!done()) { |
+ if (s[index] == "=") break; |
+ index++; |
+ } |
+ return s.substring(start, index).trim(); |
+ } |
+ |
+ String parseValue() { |
+ int start = index; |
+ while (!done()) { |
+ if (s[index] == ";") break; |
+ index++; |
+ } |
+ return s.substring(start, index).trim(); |
+ } |
+ |
+ void expect(String expected) { |
+ if (done()) throw new HttpException("Failed to parse header value [$s]"); |
+ if (s[index] != expected) { |
+ throw new HttpException("Failed to parse header value [$s]"); |
+ } |
+ index++; |
+ } |
+ |
+ void parseAttributes() { |
+ String parseAttributeName() { |
+ int start = index; |
+ while (!done()) { |
+ if (s[index] == "=" || s[index] == ";") break; |
+ index++; |
+ } |
+ return s.substring(start, index).trim().toLowerCase(); |
+ } |
+ |
+ String parseAttributeValue() { |
+ int start = index; |
+ while (!done()) { |
+ if (s[index] == ";") break; |
+ index++; |
+ } |
+ return s.substring(start, index).trim().toLowerCase(); |
+ } |
+ |
+ while (!done()) { |
+ String name = parseAttributeName(); |
+ String value = ""; |
+ if (!done() && s[index] == "=") { |
+ index++; // Skip the = character. |
+ value = parseAttributeValue(); |
+ } |
+ if (name == "expires") { |
+ expires = HttpDate._parseCookieDate(value); |
+ } else if (name == "max-age") { |
+ maxAge = int.parse(value); |
+ } else if (name == "domain") { |
+ domain = value; |
+ } else if (name == "path") { |
+ path = value; |
+ } else if (name == "httponly") { |
+ httpOnly = true; |
+ } else if (name == "secure") { |
+ secure = true; |
+ } |
+ if (!done()) index++; // Skip the ; character |
+ } |
+ } |
+ |
+ name = parseName(); |
+ if (done() || name.length == 0) { |
+ throw new HttpException("Failed to parse header value [$s]"); |
+ } |
+ index++; // Skip the = character. |
+ value = parseValue(); |
+ _validate(); |
+ if (done()) return; |
+ index++; // Skip the ; character. |
+ parseAttributes(); |
+ } |
+ |
+ String toString() { |
+ StringBuffer sb = new StringBuffer(); |
+ sb..write(name)..write("=")..write(value); |
+ if (expires != null) { |
+ sb..write("; Expires=")..write(HttpDate.format(expires)); |
+ } |
+ if (maxAge != null) { |
+ sb..write("; Max-Age=")..write(maxAge); |
+ } |
+ if (domain != null) { |
+ sb..write("; Domain=")..write(domain); |
+ } |
+ if (path != null) { |
+ sb..write("; Path=")..write(path); |
+ } |
+ if (secure) sb.write("; Secure"); |
+ if (httpOnly) sb.write("; HttpOnly"); |
+ return sb.toString(); |
+ } |
+ |
+ void _validate() { |
+ const SEPERATORS = const [ |
+ "(", ")", "<", ">", "@", ",", ";", ":", "\\", |
+ '"', "/", "[", "]", "?", "=", "{", "}"]; |
+ for (int i = 0; i < name.length; i++) { |
+ int codeUnit = name.codeUnits[i]; |
+ if (codeUnit <= 32 || |
+ codeUnit >= 127 || |
+ SEPERATORS.indexOf(name[i]) >= 0) { |
+ throw new FormatException( |
+ "Invalid character in cookie name, code unit: '$codeUnit'"); |
+ } |
+ } |
+ for (int i = 0; i < value.length; i++) { |
+ int codeUnit = value.codeUnits[i]; |
+ if (!(codeUnit == 0x21 || |
+ (codeUnit >= 0x23 && codeUnit <= 0x2B) || |
+ (codeUnit >= 0x2D && codeUnit <= 0x3A) || |
+ (codeUnit >= 0x3C && codeUnit <= 0x5B) || |
+ (codeUnit >= 0x5D && codeUnit <= 0x7E))) { |
+ throw new FormatException( |
+ "Invalid character in cookie value, code unit: '$codeUnit'"); |
+ } |
+ } |
+ } |
+} |