| Index: sdk/lib/io/http_headers.dart
|
| diff --git a/sdk/lib/io/http_headers.dart b/sdk/lib/io/http_headers.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..55a9a8957f87c205664b41fe62363b35bf8e5272
|
| --- /dev/null
|
| +++ b/sdk/lib/io/http_headers.dart
|
| @@ -0,0 +1,628 @@
|
| +// Copyright (c) 2012, 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.
|
| +
|
| +class _HttpHeaders implements HttpHeaders {
|
| + _HttpHeaders() : _headers = new Map<String, List<String>>();
|
| +
|
| + List<String> operator[](String name) {
|
| + name = name.toLowerCase();
|
| + return _headers[name];
|
| + }
|
| +
|
| + 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, Object value) {
|
| + _checkMutable();
|
| + if (value is List) {
|
| + for (int i = 0; i < value.length; i++) {
|
| + _add(name, value[i]);
|
| + }
|
| + } else {
|
| + _add(name, value);
|
| + }
|
| + }
|
| +
|
| + void set(String name, Object value) {
|
| + name = name.toLowerCase();
|
| + _checkMutable();
|
| + removeAll(name);
|
| + add(name, value);
|
| + }
|
| +
|
| + void remove(String name, Object value) {
|
| + _checkMutable();
|
| + name = name.toLowerCase();
|
| + List<String> values = _headers[name];
|
| + if (values != null) {
|
| + int index = values.indexOf(value);
|
| + if (index != -1) {
|
| + values.removeRange(index, 1);
|
| + }
|
| + }
|
| + }
|
| +
|
| + void removeAll(String name) {
|
| + _checkMutable();
|
| + name = name.toLowerCase();
|
| + _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);
|
| + }
|
| +
|
| + 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();
|
| + }
|
| +
|
| + Date get ifModifiedSince {
|
| + List<String> values = _headers["if-modified-since"];
|
| + if (values != null) {
|
| + try {
|
| + return _HttpUtils.parseDate(values[0]);
|
| + } on Exception catch (e) {
|
| + return null;
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + void set ifModifiedSince(Date ifModifiedSince) {
|
| + _checkMutable();
|
| + // Format "ifModifiedSince" header with date in Greenwich Mean Time (GMT).
|
| + String formatted = _HttpUtils.formatDate(ifModifiedSince.toUtc());
|
| + _set("if-modified-since", formatted);
|
| + }
|
| +
|
| + Date get date {
|
| + List<String> values = _headers["date"];
|
| + if (values != null) {
|
| + try {
|
| + return _HttpUtils.parseDate(values[0]);
|
| + } on Exception catch (e) {
|
| + return null;
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + void set date(Date date) {
|
| + _checkMutable();
|
| + // Format "Date" header with date in Greenwich Mean Time (GMT).
|
| + String formatted = _HttpUtils.formatDate(date.toUtc());
|
| + _set("date", formatted);
|
| + }
|
| +
|
| + Date get expires {
|
| + List<String> values = _headers["expires"];
|
| + if (values != null) {
|
| + try {
|
| + return _HttpUtils.parseDate(values[0]);
|
| + } on Exception catch (e) {
|
| + return null;
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + void set expires(Date expires) {
|
| + _checkMutable();
|
| + // Format "Expires" header with date in Greenwich Mean Time (GMT).
|
| + String formatted = _HttpUtils.formatDate(expires.toUtc());
|
| + _set("expires", formatted);
|
| + }
|
| +
|
| + ContentType get contentType {
|
| + var values = _headers["content-type"];
|
| + if (values != null) {
|
| + return new ContentType.fromString(values[0]);
|
| + } else {
|
| + return new ContentType();
|
| + }
|
| + }
|
| +
|
| + void set contentType(ContentType contentType) {
|
| + _checkMutable();
|
| + _set("content-type", contentType.toString());
|
| + }
|
| +
|
| + void _add(String name, Object value) {
|
| + var lowerCaseName = name.toLowerCase();
|
| + // TODO(sgjesse): Add immutable state throw HttpException is immutable.
|
| + if (lowerCaseName == "date") {
|
| + if (value is Date) {
|
| + date = value;
|
| + } else if (value is String) {
|
| + _set("date", value);
|
| + } else {
|
| + throw new HttpException("Unexpected type for header named $name");
|
| + }
|
| + } else if (lowerCaseName == "expires") {
|
| + if (value is Date) {
|
| + expires = value;
|
| + } else if (value is String) {
|
| + _set("expires", value);
|
| + } else {
|
| + throw new HttpException("Unexpected type for header named $name");
|
| + }
|
| + } else if (lowerCaseName == "if-modified-since") {
|
| + if (value is Date) {
|
| + ifModifiedSince = value;
|
| + } else if (value is String) {
|
| + _set("if-modified-since", value);
|
| + } else {
|
| + throw new HttpException("Unexpected type for header named $name");
|
| + }
|
| + } else if (lowerCaseName == "host") {
|
| + 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 = parseInt(value.substring(pos + 1));
|
| + } on FormatException catch (e) {
|
| + _port = null;
|
| + }
|
| + }
|
| + }
|
| + _set("host", value);
|
| + } else if (lowerCaseName == "content-type") {
|
| + _set("content-type", value);
|
| + } else {
|
| + name = lowerCaseName;
|
| + List<String> values = _headers[name];
|
| + if (values == null) {
|
| + values = new List<String>();
|
| + _headers[name] = values;
|
| + }
|
| + if (value is Date) {
|
| + values.add(_HttpUtils.formatDate(value));
|
| + } else {
|
| + values.add(value.toString());
|
| + }
|
| + }
|
| + }
|
| +
|
| + void _set(String name, String value) {
|
| + name = name.toLowerCase();
|
| + 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 == HttpClient.DEFAULT_HTTP_PORT;
|
| + String portPart = defaultPort ? "" : ":$_port";
|
| + _set("host", "$host$portPart");
|
| + }
|
| +
|
| + _foldHeader(String name) {
|
| + if (name == "set-cookie" ||
|
| + (_noFoldingHeaders != null &&
|
| + _noFoldingHeaders.indexOf(name) != -1)) {
|
| + return false;
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + _write(_HttpConnectionBase connection) {
|
| + final COLONSP = const [_CharCode.COLON, _CharCode.SP];
|
| + final COMMASP = const [_CharCode.COMMA, _CharCode.SP];
|
| + final CRLF = const [_CharCode.CR, _CharCode.LF];
|
| +
|
| + var bufferSize = 16 * 1024;
|
| + var buffer = new Uint8List(bufferSize);
|
| + var bufferPos = 0;
|
| +
|
| + void writeBuffer() {
|
| + connection._writeFrom(buffer, 0, bufferPos);
|
| + bufferPos = 0;
|
| + }
|
| +
|
| + // Format headers.
|
| + _headers.forEach((String name, List<String> values) {
|
| + bool fold = _foldHeader(name);
|
| + List<int> nameData;
|
| + nameData = name.charCodes;
|
| + int nameDataLen = nameData.length;
|
| + if (nameDataLen + 2 > bufferSize - bufferPos) writeBuffer();
|
| + buffer.setRange(bufferPos, nameDataLen, nameData);
|
| + bufferPos += nameDataLen;
|
| + buffer[bufferPos++] = _CharCode.COLON;
|
| + buffer[bufferPos++] = _CharCode.SP;
|
| + for (int i = 0; i < values.length; i++) {
|
| + List<int> data = values[i].charCodes;
|
| + int dataLen = data.length;
|
| + // Worst case here is writing the name, value and 6 additional bytes.
|
| + if (nameDataLen + dataLen + 6 > bufferSize - bufferPos) writeBuffer();
|
| + if (i > 0) {
|
| + if (fold) {
|
| + buffer[bufferPos++] = _CharCode.COMMA;
|
| + buffer[bufferPos++] = _CharCode.SP;
|
| + } else {
|
| + buffer[bufferPos++] = _CharCode.CR;
|
| + buffer[bufferPos++] = _CharCode.LF;
|
| + buffer.setRange(bufferPos, nameDataLen, nameData);
|
| + bufferPos += nameDataLen;
|
| + buffer[bufferPos++] = _CharCode.COLON;
|
| + buffer[bufferPos++] = _CharCode.SP;
|
| + }
|
| + }
|
| + buffer.setRange(bufferPos, dataLen, data);
|
| + bufferPos += dataLen;
|
| + }
|
| + buffer[bufferPos++] = _CharCode.CR;
|
| + buffer[bufferPos++] = _CharCode.LF;
|
| + });
|
| + writeBuffer();
|
| + }
|
| +
|
| + String toString() {
|
| + StringBuffer sb = new StringBuffer();
|
| + _headers.forEach((String name, List<String> values) {
|
| + sb.add(name);
|
| + sb.add(": ");
|
| + bool fold = _foldHeader(name);
|
| + for (int i = 0; i < values.length; i++) {
|
| + if (i > 0) {
|
| + if (fold) {
|
| + sb.add(", ");
|
| + } else {
|
| + sb.add("\n");
|
| + sb.add(name);
|
| + sb.add(": ");
|
| + }
|
| + }
|
| + sb.add(values[i]);
|
| + }
|
| + sb.add("\n");
|
| + });
|
| + return sb.toString();
|
| + }
|
| +
|
| + bool _mutable = true; // Are the headers currently mutable?
|
| + Map<String, List<String>> _headers;
|
| + List<String> _noFoldingHeaders;
|
| +
|
| + String _host;
|
| + int _port;
|
| +}
|
| +
|
| +
|
| +class _HeaderValue implements HeaderValue {
|
| + _HeaderValue([String this.value = ""]);
|
| +
|
| + _HeaderValue.fromString(String value, {this.parameterSeparator: ";"}) {
|
| + // Parse the string.
|
| + _parse(value);
|
| + }
|
| +
|
| + Map<String, String> get parameters {
|
| + if (_parameters == null) _parameters = new Map<String, String>();
|
| + return _parameters;
|
| + }
|
| +
|
| + String toString() {
|
| + StringBuffer sb = new StringBuffer();
|
| + sb.add(value);
|
| + if (parameters != null && parameters.length > 0) {
|
| + _parameters.forEach((String name, String value) {
|
| + sb.add("; ");
|
| + sb.add(name);
|
| + sb.add("=");
|
| + sb.add(value);
|
| + });
|
| + }
|
| + return sb.toString();
|
| + }
|
| +
|
| + void _parse(String s) {
|
| + 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] == parameterSeparator) break;
|
| + index++;
|
| + }
|
| + return s.substring(start, index).toLowerCase();
|
| + }
|
| +
|
| + 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() {
|
| + _parameters = new Map<String, String>();
|
| +
|
| + String parseParameterName() {
|
| + int start = index;
|
| + while (!done()) {
|
| + if (s[index] == " " || s[index] == "\t" || s[index] == "=") break;
|
| + index++;
|
| + }
|
| + return s.substring(start, index).toLowerCase();
|
| + }
|
| +
|
| + String parseParameterValue() {
|
| + if (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");
|
| + }
|
| + index++;
|
| + } else if (s[index] == "\"") {
|
| + index++;
|
| + break;
|
| + }
|
| + sb.add(s[index]);
|
| + index++;
|
| + }
|
| + return sb.toString();
|
| + } else {
|
| + // Parse non-quoted value.
|
| + return parseValue();
|
| + }
|
| + }
|
| +
|
| + while (!done()) {
|
| + skipWS();
|
| + if (done()) return;
|
| + String name = parseParameterName();
|
| + skipWS();
|
| + expect("=");
|
| + skipWS();
|
| + String value = parseParameterValue();
|
| + _parameters[name] = value;
|
| + skipWS();
|
| + if (done()) return;
|
| + expect(parameterSeparator);
|
| + }
|
| + }
|
| +
|
| + skipWS();
|
| + value = parseValue();
|
| + skipWS();
|
| + if (done()) return;
|
| + maybeExpect(parameterSeparator);
|
| + parseParameters();
|
| + }
|
| +
|
| + String value;
|
| + String parameterSeparator;
|
| + Map<String, String> _parameters;
|
| +}
|
| +
|
| +
|
| +class _ContentType extends _HeaderValue implements ContentType {
|
| + _ContentType(String primaryType, String subType)
|
| + : _primaryType = primaryType, _subType = subType, super("");
|
| +
|
| + _ContentType.fromString(String value) : super.fromString(value);
|
| +
|
| + String get value => "$_primaryType/$_subType";
|
| +
|
| + void set value(String s) {
|
| + int index = s.indexOf("/");
|
| + if (index == -1 || index == (s.length - 1)) {
|
| + primaryType = s.trim().toLowerCase();
|
| + subType = "";
|
| + } else {
|
| + primaryType = s.substring(0, index).trim().toLowerCase();
|
| + subType = s.substring(index + 1).trim().toLowerCase();
|
| + }
|
| + }
|
| +
|
| + String get primaryType => _primaryType;
|
| +
|
| + void set primaryType(String s) {
|
| + _primaryType = s;
|
| + }
|
| +
|
| + String get subType => _subType;
|
| +
|
| + void set subType(String s) {
|
| + _subType = s;
|
| + }
|
| +
|
| + String get charset => parameters["charset"];
|
| +
|
| + void set charset(String s) {
|
| + parameters["charset"] = s;
|
| + }
|
| +
|
| + String _primaryType = "";
|
| + String _subType = "";
|
| +}
|
| +
|
| +
|
| +class _Cookie implements Cookie {
|
| + _Cookie([String this.name, String this.value]);
|
| +
|
| + _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().toLowerCase();
|
| + }
|
| +
|
| + String parseValue() {
|
| + int start = index;
|
| + while (!done()) {
|
| + if (s[index] == ";") break;
|
| + index++;
|
| + }
|
| + return s.substring(start, index).trim().toLowerCase();
|
| + }
|
| +
|
| + 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 = _HttpUtils.parseCookieDate(value);
|
| + } else if (name == "max-age") {
|
| + maxAge = parseInt(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();
|
| + if (done()) return;
|
| + index++; // Skip the ; character.
|
| + parseAttributes();
|
| + }
|
| +
|
| + String toString() {
|
| + StringBuffer sb = new StringBuffer();
|
| + sb.add(name);
|
| + sb.add("=");
|
| + sb.add(value);
|
| + if (expires != null) {
|
| + sb.add("; Expires=");
|
| + sb.add(_HttpUtils.formatDate(expires));
|
| + }
|
| + if (maxAge != null) {
|
| + sb.add("; Max-Age=");
|
| + sb.add(maxAge);
|
| + }
|
| + if (domain != null) {
|
| + sb.add("; Domain=");
|
| + sb.add(domain);
|
| + }
|
| + if (path != null) {
|
| + sb.add("; Path=");
|
| + sb.add(path);
|
| + }
|
| + if (secure) sb.add("; Secure");
|
| + if (httpOnly) sb.add("; HttpOnly");
|
| + return sb.toString();
|
| + }
|
| +
|
| + String name;
|
| + String value;
|
| + Date expires;
|
| + int maxAge;
|
| + String domain;
|
| + String path;
|
| + bool httpOnly = false;
|
| + bool secure = false;
|
| +}
|
|
|