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 |