| 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; | 8 final Map<String, List<String>> _headers; |
| 9 final String protocolVersion; | 9 final String protocolVersion; |
| 10 | 10 |
| 11 bool _mutable = true; // Are the headers currently mutable? | 11 bool _mutable = true; // Are the headers currently mutable? |
| 12 List<String> _noFoldingHeaders; | 12 List<String> _noFoldingHeaders; |
| 13 | 13 |
| 14 int _contentLength = -1; | 14 int _contentLength = -1; |
| 15 bool _persistentConnection = true; | 15 bool _persistentConnection = true; |
| 16 bool _chunkedTransferEncoding = false; | 16 bool _chunkedTransferEncoding = false; |
| 17 String _host; | 17 String _host; |
| 18 int _port; | 18 int _port; |
| 19 | 19 |
| 20 final int _defaultPortForScheme; | 20 final int _defaultPortForScheme; |
| 21 | 21 |
| 22 _HttpHeaders(this.protocolVersion, | 22 _HttpHeaders(this.protocolVersion, |
| 23 {int defaultPortForScheme: HttpClient.DEFAULT_HTTP_PORT, | 23 {int defaultPortForScheme: HttpClient.DEFAULT_HTTP_PORT, |
| 24 _HttpHeaders initialHeaders}) | 24 _HttpHeaders initialHeaders}) |
| 25 : _headers = new HashMap<String, List<String>>(), | 25 : _headers = new HashMap<String, List<String>>(), |
| 26 _defaultPortForScheme = defaultPortForScheme { | 26 _defaultPortForScheme = defaultPortForScheme { |
| 27 if (initialHeaders != null) { | 27 if (initialHeaders != null) { |
| 28 initialHeaders._headers.forEach((name, value) => _headers[name] = value); | 28 initialHeaders._headers.forEach((name, value) => _headers[name] = value); |
| 29 _contentLength = initialHeaders._contentLength; | 29 _contentLength = initialHeaders._contentLength; |
| 30 _persistentConnection = initialHeaders._persistentConnection; | 30 _persistentConnection = initialHeaders._persistentConnection; |
| 31 _chunkedTransferEncoding = initialHeaders._chunkedTransferEncoding; | 31 _chunkedTransferEncoding = initialHeaders._chunkedTransferEncoding; |
| 32 _host = initialHeaders._host; | 32 _host = initialHeaders._host; |
| 33 _port = initialHeaders._port; | 33 _port = initialHeaders._port; |
| 34 } | 34 } |
| 35 if (protocolVersion == "1.0") { | 35 if (protocolVersion == "1.0") { |
| 36 _persistentConnection = false; | 36 _persistentConnection = false; |
| 37 _chunkedTransferEncoding = false; | 37 _chunkedTransferEncoding = false; |
| 38 } | 38 } |
| 39 } | 39 } |
| 40 | 40 |
| 41 List<String> operator[](String name) => _headers[name.toLowerCase()]; | 41 List<String> operator [](String name) => _headers[name.toLowerCase()]; |
| 42 | 42 |
| 43 String value(String name) { | 43 String value(String name) { |
| 44 name = name.toLowerCase(); | 44 name = name.toLowerCase(); |
| 45 List<String> values = _headers[name]; | 45 List<String> values = _headers[name]; |
| 46 if (values == null) return null; | 46 if (values == null) return null; |
| 47 if (values.length > 1) { | 47 if (values.length > 1) { |
| 48 throw new HttpException("More than one value for header $name"); | 48 throw new HttpException("More than one value for header $name"); |
| 49 } | 49 } |
| 50 return values[0]; | 50 return values[0]; |
| 51 } | 51 } |
| (...skipping 395 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 447 if (!_mutable) throw new HttpException("HTTP headers are not mutable"); | 447 if (!_mutable) throw new HttpException("HTTP headers are not mutable"); |
| 448 } | 448 } |
| 449 | 449 |
| 450 _updateHostHeader() { | 450 _updateHostHeader() { |
| 451 bool defaultPort = _port == null || _port == _defaultPortForScheme; | 451 bool defaultPort = _port == null || _port == _defaultPortForScheme; |
| 452 _set("host", defaultPort ? host : "$host:$_port"); | 452 _set("host", defaultPort ? host : "$host:$_port"); |
| 453 } | 453 } |
| 454 | 454 |
| 455 _foldHeader(String name) { | 455 _foldHeader(String name) { |
| 456 if (name == HttpHeaders.SET_COOKIE || | 456 if (name == HttpHeaders.SET_COOKIE || |
| 457 (_noFoldingHeaders != null && | 457 (_noFoldingHeaders != null && _noFoldingHeaders.indexOf(name) != -1)) { |
| 458 _noFoldingHeaders.indexOf(name) != -1)) { | |
| 459 return false; | 458 return false; |
| 460 } | 459 } |
| 461 return true; | 460 return true; |
| 462 } | 461 } |
| 463 | 462 |
| 464 void _finalize() { | 463 void _finalize() { |
| 465 _mutable = false; | 464 _mutable = false; |
| 466 } | 465 } |
| 467 | 466 |
| 468 void _build(BytesBuilder builder) { | 467 void _build(BytesBuilder builder) { |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 516 List<Cookie> _parseCookies() { | 515 List<Cookie> _parseCookies() { |
| 517 // Parse a Cookie header value according to the rules in RFC 6265. | 516 // Parse a Cookie header value according to the rules in RFC 6265. |
| 518 var cookies = new List<Cookie>(); | 517 var cookies = new List<Cookie>(); |
| 519 void parseCookieString(String s) { | 518 void parseCookieString(String s) { |
| 520 int index = 0; | 519 int index = 0; |
| 521 | 520 |
| 522 bool done() => index == -1 || index == s.length; | 521 bool done() => index == -1 || index == s.length; |
| 523 | 522 |
| 524 void skipWS() { | 523 void skipWS() { |
| 525 while (!done()) { | 524 while (!done()) { |
| 526 if (s[index] != " " && s[index] != "\t") return; | 525 if (s[index] != " " && s[index] != "\t") return; |
| 527 index++; | 526 index++; |
| 528 } | 527 } |
| 529 } | 528 } |
| 530 | 529 |
| 531 String parseName() { | 530 String parseName() { |
| 532 int start = index; | 531 int start = index; |
| 533 while (!done()) { | 532 while (!done()) { |
| 534 if (s[index] == " " || s[index] == "\t" || s[index] == "=") break; | 533 if (s[index] == " " || s[index] == "\t" || s[index] == "=") break; |
| 535 index++; | 534 index++; |
| 536 } | 535 } |
| 537 return s.substring(start, index); | 536 return s.substring(start, index); |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 570 // Skip it, invalid cookie data. | 569 // Skip it, invalid cookie data. |
| 571 } | 570 } |
| 572 skipWS(); | 571 skipWS(); |
| 573 if (done()) return; | 572 if (done()) return; |
| 574 if (!expect(";")) { | 573 if (!expect(";")) { |
| 575 index = s.indexOf(';', index); | 574 index = s.indexOf(';', index); |
| 576 continue; | 575 continue; |
| 577 } | 576 } |
| 578 } | 577 } |
| 579 } | 578 } |
| 579 |
| 580 List<String> values = _headers[HttpHeaders.COOKIE]; | 580 List<String> values = _headers[HttpHeaders.COOKIE]; |
| 581 if (values != null) { | 581 if (values != null) { |
| 582 values.forEach((headerValue) => parseCookieString(headerValue)); | 582 values.forEach((headerValue) => parseCookieString(headerValue)); |
| 583 } | 583 } |
| 584 return cookies; | 584 return cookies; |
| 585 } | 585 } |
| 586 | 586 |
| 587 static String _validateField(String field) { | 587 static String _validateField(String field) { |
| 588 for (var i = 0; i < field.length; i++) { | 588 for (var i = 0; i < field.length; i++) { |
| 589 if (!_HttpParser._isTokenChar(field.codeUnitAt(i))) { | 589 if (!_HttpParser._isTokenChar(field.codeUnitAt(i))) { |
| 590 throw new FormatException( | 590 throw new FormatException( |
| 591 "Invalid HTTP header field name: ${JSON.encode(field)}"); | 591 "Invalid HTTP header field name: ${JSON.encode(field)}"); |
| 592 } | 592 } |
| 593 } | 593 } |
| 594 return field.toLowerCase(); | 594 return field.toLowerCase(); |
| 595 } | 595 } |
| 596 | 596 |
| 597 static _validateValue(value) { | 597 static _validateValue(value) { |
| 598 if (value is! String) return value; | 598 if (value is! String) return value; |
| 599 for (var i = 0; i < value.length; i++) { | 599 for (var i = 0; i < value.length; i++) { |
| 600 if (!_HttpParser._isValueChar(value.codeUnitAt(i))) { | 600 if (!_HttpParser._isValueChar(value.codeUnitAt(i))) { |
| 601 throw new FormatException( | 601 throw new FormatException( |
| 602 "Invalid HTTP header field value: ${JSON.encode(value)}"); | 602 "Invalid HTTP header field value: ${JSON.encode(value)}"); |
| 603 } | 603 } |
| 604 } | 604 } |
| 605 return value; | 605 return value; |
| 606 } | 606 } |
| 607 } | 607 } |
| 608 | 608 |
| 609 | |
| 610 class _HeaderValue implements HeaderValue { | 609 class _HeaderValue implements HeaderValue { |
| 611 String _value; | 610 String _value; |
| 612 Map<String, String> _parameters; | 611 Map<String, String> _parameters; |
| 613 Map<String, String> _unmodifiableParameters; | 612 Map<String, String> _unmodifiableParameters; |
| 614 | 613 |
| 615 _HeaderValue([this._value = "", Map<String, String> parameters]) { | 614 _HeaderValue([this._value = "", Map<String, String> parameters]) { |
| 616 if (parameters != null) { | 615 if (parameters != null) { |
| 617 _parameters = new HashMap<String, String>.from(parameters); | 616 _parameters = new HashMap<String, String>.from(parameters); |
| 618 } | 617 } |
| 619 } | 618 } |
| 620 | 619 |
| 621 static _HeaderValue parse(String value, | 620 static _HeaderValue parse(String value, |
| 622 {parameterSeparator: ";", | 621 {parameterSeparator: ";", |
| 623 valueSeparator: null, | 622 valueSeparator: null, |
| 624 preserveBackslash: false}) { | 623 preserveBackslash: false}) { |
| 625 // Parse the string. | 624 // Parse the string. |
| 626 var result = new _HeaderValue(); | 625 var result = new _HeaderValue(); |
| 627 result._parse(value, parameterSeparator, valueSeparator, preserveBackslash); | 626 result._parse(value, parameterSeparator, valueSeparator, preserveBackslash); |
| 628 return result; | 627 return result; |
| 629 } | 628 } |
| 630 | 629 |
| 631 String get value => _value; | 630 String get value => _value; |
| 632 | 631 |
| 633 void _ensureParameters() { | 632 void _ensureParameters() { |
| 634 if (_parameters == null) { | 633 if (_parameters == null) { |
| (...skipping 13 matching lines...) Expand all Loading... |
| 648 StringBuffer sb = new StringBuffer(); | 647 StringBuffer sb = new StringBuffer(); |
| 649 sb.write(_value); | 648 sb.write(_value); |
| 650 if (parameters != null && parameters.length > 0) { | 649 if (parameters != null && parameters.length > 0) { |
| 651 _parameters.forEach((String name, String value) { | 650 _parameters.forEach((String name, String value) { |
| 652 sb..write("; ")..write(name)..write("=")..write(value); | 651 sb..write("; ")..write(name)..write("=")..write(value); |
| 653 }); | 652 }); |
| 654 } | 653 } |
| 655 return sb.toString(); | 654 return sb.toString(); |
| 656 } | 655 } |
| 657 | 656 |
| 658 void _parse(String s, | 657 void _parse(String s, String parameterSeparator, String valueSeparator, |
| 659 String parameterSeparator, | 658 bool preserveBackslash) { |
| 660 String valueSeparator, | |
| 661 bool preserveBackslash) { | |
| 662 int index = 0; | 659 int index = 0; |
| 663 | 660 |
| 664 bool done() => index == s.length; | 661 bool done() => index == s.length; |
| 665 | 662 |
| 666 void skipWS() { | 663 void skipWS() { |
| 667 while (!done()) { | 664 while (!done()) { |
| 668 if (s[index] != " " && s[index] != "\t") return; | 665 if (s[index] != " " && s[index] != "\t") return; |
| 669 index++; | 666 index++; |
| 670 } | 667 } |
| 671 } | 668 } |
| (...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 756 } | 753 } |
| 757 String value = parseParameterValue(); | 754 String value = parseParameterValue(); |
| 758 if (name == 'charset' && this is _ContentType && value != null) { | 755 if (name == 'charset' && this is _ContentType && value != null) { |
| 759 // Charset parameter of ContentTypes are always lower-case. | 756 // Charset parameter of ContentTypes are always lower-case. |
| 760 value = value.toLowerCase(); | 757 value = value.toLowerCase(); |
| 761 } | 758 } |
| 762 parameters[name] = value; | 759 parameters[name] = value; |
| 763 skipWS(); | 760 skipWS(); |
| 764 if (done()) return; | 761 if (done()) return; |
| 765 // TODO: Implement support for multi-valued parameters. | 762 // TODO: Implement support for multi-valued parameters. |
| 766 if(s[index] == valueSeparator) return; | 763 if (s[index] == valueSeparator) return; |
| 767 expect(parameterSeparator); | 764 expect(parameterSeparator); |
| 768 } | 765 } |
| 769 } | 766 } |
| 770 | 767 |
| 771 skipWS(); | 768 skipWS(); |
| 772 _value = parseValue(); | 769 _value = parseValue(); |
| 773 skipWS(); | 770 skipWS(); |
| 774 if (done()) return; | 771 if (done()) return; |
| 775 maybeExpect(parameterSeparator); | 772 maybeExpect(parameterSeparator); |
| 776 parseParameters(); | 773 parseParameters(); |
| 777 } | 774 } |
| 778 } | 775 } |
| 779 | 776 |
| 780 | |
| 781 class _ContentType extends _HeaderValue implements ContentType { | 777 class _ContentType extends _HeaderValue implements ContentType { |
| 782 String _primaryType = ""; | 778 String _primaryType = ""; |
| 783 String _subType = ""; | 779 String _subType = ""; |
| 784 | 780 |
| 785 _ContentType(String primaryType, | 781 _ContentType(String primaryType, String subType, String charset, |
| 786 String subType, | 782 Map<String, String> parameters) |
| 787 String charset, | 783 : _primaryType = primaryType, |
| 788 Map<String, String> parameters) | 784 _subType = subType, |
| 789 : _primaryType = primaryType, _subType = subType, super("") { | 785 super("") { |
| 790 if (_primaryType == null) _primaryType = ""; | 786 if (_primaryType == null) _primaryType = ""; |
| 791 if (_subType == null) _subType = ""; | 787 if (_subType == null) _subType = ""; |
| 792 _value = "$_primaryType/$_subType"; | 788 _value = "$_primaryType/$_subType"; |
| 793 if (parameters != null) { | 789 if (parameters != null) { |
| 794 _ensureParameters(); | 790 _ensureParameters(); |
| 795 parameters.forEach((String key, String value) { | 791 parameters.forEach((String key, String value) { |
| 796 String lowerCaseKey = key.toLowerCase(); | 792 String lowerCaseKey = key.toLowerCase(); |
| 797 if (lowerCaseKey == "charset") { | 793 if (lowerCaseKey == "charset") { |
| 798 value = value.toLowerCase(); | 794 value = value.toLowerCase(); |
| 799 } | 795 } |
| (...skipping 25 matching lines...) Expand all Loading... |
| 825 | 821 |
| 826 String get mimeType => '$primaryType/$subType'; | 822 String get mimeType => '$primaryType/$subType'; |
| 827 | 823 |
| 828 String get primaryType => _primaryType; | 824 String get primaryType => _primaryType; |
| 829 | 825 |
| 830 String get subType => _subType; | 826 String get subType => _subType; |
| 831 | 827 |
| 832 String get charset => parameters["charset"]; | 828 String get charset => parameters["charset"]; |
| 833 } | 829 } |
| 834 | 830 |
| 835 | |
| 836 class _Cookie implements Cookie { | 831 class _Cookie implements Cookie { |
| 837 String name; | 832 String name; |
| 838 String value; | 833 String value; |
| 839 DateTime expires; | 834 DateTime expires; |
| 840 int maxAge; | 835 int maxAge; |
| 841 String domain; | 836 String domain; |
| 842 String path; | 837 String path; |
| 843 bool httpOnly = false; | 838 bool httpOnly = false; |
| 844 bool secure = false; | 839 bool secure = false; |
| 845 | 840 |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 902 if (s[index] == ";") break; | 897 if (s[index] == ";") break; |
| 903 index++; | 898 index++; |
| 904 } | 899 } |
| 905 return s.substring(start, index).trim().toLowerCase(); | 900 return s.substring(start, index).trim().toLowerCase(); |
| 906 } | 901 } |
| 907 | 902 |
| 908 while (!done()) { | 903 while (!done()) { |
| 909 String name = parseAttributeName(); | 904 String name = parseAttributeName(); |
| 910 String value = ""; | 905 String value = ""; |
| 911 if (!done() && s[index] == "=") { | 906 if (!done() && s[index] == "=") { |
| 912 index++; // Skip the = character. | 907 index++; // Skip the = character. |
| 913 value = parseAttributeValue(); | 908 value = parseAttributeValue(); |
| 914 } | 909 } |
| 915 if (name == "expires") { | 910 if (name == "expires") { |
| 916 expires = HttpDate._parseCookieDate(value); | 911 expires = HttpDate._parseCookieDate(value); |
| 917 } else if (name == "max-age") { | 912 } else if (name == "max-age") { |
| 918 maxAge = int.parse(value); | 913 maxAge = int.parse(value); |
| 919 } else if (name == "domain") { | 914 } else if (name == "domain") { |
| 920 domain = value; | 915 domain = value; |
| 921 } else if (name == "path") { | 916 } else if (name == "path") { |
| 922 path = value; | 917 path = value; |
| 923 } else if (name == "httponly") { | 918 } else if (name == "httponly") { |
| 924 httpOnly = true; | 919 httpOnly = true; |
| 925 } else if (name == "secure") { | 920 } else if (name == "secure") { |
| 926 secure = true; | 921 secure = true; |
| 927 } | 922 } |
| 928 if (!done()) index++; // Skip the ; character | 923 if (!done()) index++; // Skip the ; character |
| 929 } | 924 } |
| 930 } | 925 } |
| 931 | 926 |
| 932 name = parseName(); | 927 name = parseName(); |
| 933 if (done() || name.length == 0) { | 928 if (done() || name.length == 0) { |
| 934 throw new HttpException("Failed to parse header value [$s]"); | 929 throw new HttpException("Failed to parse header value [$s]"); |
| 935 } | 930 } |
| 936 index++; // Skip the = character. | 931 index++; // Skip the = character. |
| 937 value = parseValue(); | 932 value = parseValue(); |
| 938 _validate(); | 933 _validate(); |
| 939 if (done()) return; | 934 if (done()) return; |
| 940 index++; // Skip the ; character. | 935 index++; // Skip the ; character. |
| 941 parseAttributes(); | 936 parseAttributes(); |
| 942 } | 937 } |
| 943 | 938 |
| 944 String toString() { | 939 String toString() { |
| 945 StringBuffer sb = new StringBuffer(); | 940 StringBuffer sb = new StringBuffer(); |
| 946 sb..write(name)..write("=")..write(value); | 941 sb..write(name)..write("=")..write(value); |
| 947 if (expires != null) { | 942 if (expires != null) { |
| 948 sb..write("; Expires=")..write(HttpDate.format(expires)); | 943 sb..write("; Expires=")..write(HttpDate.format(expires)); |
| 949 } | 944 } |
| 950 if (maxAge != null) { | 945 if (maxAge != null) { |
| 951 sb..write("; Max-Age=")..write(maxAge); | 946 sb..write("; Max-Age=")..write(maxAge); |
| 952 } | 947 } |
| 953 if (domain != null) { | 948 if (domain != null) { |
| 954 sb..write("; Domain=")..write(domain); | 949 sb..write("; Domain=")..write(domain); |
| 955 } | 950 } |
| 956 if (path != null) { | 951 if (path != null) { |
| 957 sb..write("; Path=")..write(path); | 952 sb..write("; Path=")..write(path); |
| 958 } | 953 } |
| 959 if (secure) sb.write("; Secure"); | 954 if (secure) sb.write("; Secure"); |
| 960 if (httpOnly) sb.write("; HttpOnly"); | 955 if (httpOnly) sb.write("; HttpOnly"); |
| 961 return sb.toString(); | 956 return sb.toString(); |
| 962 } | 957 } |
| 963 | 958 |
| 964 void _validate() { | 959 void _validate() { |
| 965 const SEPERATORS = const [ | 960 const SEPERATORS = const [ |
| 966 "(", ")", "<", ">", "@", ",", ";", ":", "\\", | 961 "(", |
| 967 '"', "/", "[", "]", "?", "=", "{", "}"]; | 962 ")", |
| 963 "<", |
| 964 ">", |
| 965 "@", |
| 966 ",", |
| 967 ";", |
| 968 ":", |
| 969 "\\", |
| 970 '"', |
| 971 "/", |
| 972 "[", |
| 973 "]", |
| 974 "?", |
| 975 "=", |
| 976 "{", |
| 977 "}" |
| 978 ]; |
| 968 for (int i = 0; i < name.length; i++) { | 979 for (int i = 0; i < name.length; i++) { |
| 969 int codeUnit = name.codeUnits[i]; | 980 int codeUnit = name.codeUnits[i]; |
| 970 if (codeUnit <= 32 || | 981 if (codeUnit <= 32 || |
| 971 codeUnit >= 127 || | 982 codeUnit >= 127 || |
| 972 SEPERATORS.indexOf(name[i]) >= 0) { | 983 SEPERATORS.indexOf(name[i]) >= 0) { |
| 973 throw new FormatException( | 984 throw new FormatException( |
| 974 "Invalid character in cookie name, code unit: '$codeUnit'"); | 985 "Invalid character in cookie name, code unit: '$codeUnit'"); |
| 975 } | 986 } |
| 976 } | 987 } |
| 977 for (int i = 0; i < value.length; i++) { | 988 for (int i = 0; i < value.length; i++) { |
| 978 int codeUnit = value.codeUnits[i]; | 989 int codeUnit = value.codeUnits[i]; |
| 979 if (!(codeUnit == 0x21 || | 990 if (!(codeUnit == 0x21 || |
| 980 (codeUnit >= 0x23 && codeUnit <= 0x2B) || | 991 (codeUnit >= 0x23 && codeUnit <= 0x2B) || |
| 981 (codeUnit >= 0x2D && codeUnit <= 0x3A) || | 992 (codeUnit >= 0x2D && codeUnit <= 0x3A) || |
| 982 (codeUnit >= 0x3C && codeUnit <= 0x5B) || | 993 (codeUnit >= 0x3C && codeUnit <= 0x5B) || |
| 983 (codeUnit >= 0x5D && codeUnit <= 0x7E))) { | 994 (codeUnit >= 0x5D && codeUnit <= 0x7E))) { |
| 984 throw new FormatException( | 995 throw new FormatException( |
| 985 "Invalid character in cookie value, code unit: '$codeUnit'"); | 996 "Invalid character in cookie value, code unit: '$codeUnit'"); |
| 986 } | 997 } |
| 987 } | 998 } |
| 988 } | 999 } |
| 989 } | 1000 } |
| OLD | NEW |