| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, 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 library http_parser.media_type; | 5 library http_parser.media_type; |
| 6 | 6 |
| 7 import 'package:collection/collection.dart'; | 7 import 'package:collection/collection.dart'; |
| 8 import 'package:string_scanner/string_scanner.dart'; | 8 import 'package:string_scanner/string_scanner.dart'; |
| 9 | 9 |
| 10 // All of the following regular expressions come from section 2.2 of the HTTP | 10 import 'scan.dart'; |
| 11 // spec: http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html | 11 import 'utils.dart'; |
| 12 final _lws = new RegExp(r"(?:\r\n)?[ \t]+"); | |
| 13 final _token = new RegExp(r'[^()<>@,;:"\\/[\]?={} \t\x00-\x1F\x7F]+'); | |
| 14 final _quotedString = new RegExp(r'"(?:[^"\x00-\x1F\x7F]|\\.)*"'); | |
| 15 final _quotedPair = new RegExp(r'\\(.)'); | |
| 16 | |
| 17 /// A regular expression matching any number of [_lws] productions in a row. | |
| 18 final _whitespace = new RegExp("(?:${_lws.pattern})*"); | |
| 19 | |
| 20 /// A regular expression matching a character that is not a valid HTTP token. | |
| 21 final _nonToken = new RegExp(r'[()<>@,;:"\\/\[\]?={} \t\x00-\x1F\x7F]'); | |
| 22 | 12 |
| 23 /// A regular expression matching a character that needs to be backslash-escaped | 13 /// A regular expression matching a character that needs to be backslash-escaped |
| 24 /// in a quoted string. | 14 /// in a quoted string. |
| 25 final _escapedChar = new RegExp(r'["\x00-\x1F\x7F]'); | 15 final _escapedChar = new RegExp(r'["\x00-\x1F\x7F]'); |
| 26 | 16 |
| 27 /// A class representing an HTTP media type, as used in Accept and Content-Type | 17 /// A class representing an HTTP media type, as used in Accept and Content-Type |
| 28 /// headers. | 18 /// headers. |
| 29 /// | 19 /// |
| 30 /// This is immutable; new instances can be created based on an old instance by | 20 /// This is immutable; new instances can be created based on an old instance by |
| 31 /// calling [change]. | 21 /// calling [change]. |
| (...skipping 11 matching lines...) Expand all Loading... |
| 43 | 33 |
| 44 /// The media type's MIME type. | 34 /// The media type's MIME type. |
| 45 String get mimeType => "$type/$subtype"; | 35 String get mimeType => "$type/$subtype"; |
| 46 | 36 |
| 47 /// Parses a media type. | 37 /// Parses a media type. |
| 48 /// | 38 /// |
| 49 /// This will throw a FormatError if the media type is invalid. | 39 /// This will throw a FormatError if the media type is invalid. |
| 50 factory MediaType.parse(String mediaType) { | 40 factory MediaType.parse(String mediaType) { |
| 51 // This parsing is based on sections 3.6 and 3.7 of the HTTP spec: | 41 // This parsing is based on sections 3.6 and 3.7 of the HTTP spec: |
| 52 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html. | 42 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html. |
| 53 try { | 43 return wrapFormatException("media type", mediaType, () { |
| 54 var scanner = new StringScanner(mediaType); | 44 var scanner = new StringScanner(mediaType); |
| 55 scanner.scan(_whitespace); | 45 scanner.scan(whitespace); |
| 56 scanner.expect(_token); | 46 scanner.expect(token); |
| 57 var type = scanner.lastMatch[0]; | 47 var type = scanner.lastMatch[0]; |
| 58 scanner.expect('/'); | 48 scanner.expect('/'); |
| 59 scanner.expect(_token); | 49 scanner.expect(token); |
| 60 var subtype = scanner.lastMatch[0]; | 50 var subtype = scanner.lastMatch[0]; |
| 61 scanner.scan(_whitespace); | 51 scanner.scan(whitespace); |
| 62 | 52 |
| 63 var parameters = {}; | 53 var parameters = {}; |
| 64 while (scanner.scan(';')) { | 54 while (scanner.scan(';')) { |
| 65 scanner.scan(_whitespace); | 55 scanner.scan(whitespace); |
| 66 scanner.expect(_token); | 56 scanner.expect(token); |
| 67 var attribute = scanner.lastMatch[0]; | 57 var attribute = scanner.lastMatch[0]; |
| 68 scanner.expect('='); | 58 scanner.expect('='); |
| 69 | 59 |
| 70 var value; | 60 var value; |
| 71 if (scanner.scan(_token)) { | 61 if (scanner.scan(token)) { |
| 72 value = scanner.lastMatch[0]; | 62 value = scanner.lastMatch[0]; |
| 73 } else { | 63 } else { |
| 74 scanner.expect(_quotedString); | 64 value = expectQuotedString(scanner); |
| 75 var quotedString = scanner.lastMatch[0]; | |
| 76 value = quotedString | |
| 77 .substring(1, quotedString.length - 1) | |
| 78 .replaceAllMapped(_quotedPair, (match) => match[1]); | |
| 79 } | 65 } |
| 80 | 66 |
| 81 scanner.scan(_whitespace); | 67 scanner.scan(whitespace); |
| 82 parameters[attribute] = value; | 68 parameters[attribute] = value; |
| 83 } | 69 } |
| 84 | 70 |
| 85 scanner.expectDone(); | 71 scanner.expectDone(); |
| 86 return new MediaType(type, subtype, parameters); | 72 return new MediaType(type, subtype, parameters); |
| 87 } on FormatException catch (error) { | 73 }); |
| 88 throw new FormatException( | |
| 89 'Invalid media type "$mediaType": ${error.message}'); | |
| 90 } | |
| 91 } | 74 } |
| 92 | 75 |
| 93 MediaType(this.type, this.subtype, [Map<String, String> parameters]) | 76 MediaType(this.type, this.subtype, [Map<String, String> parameters]) |
| 94 : this.parameters = new UnmodifiableMapView( | 77 : this.parameters = new UnmodifiableMapView( |
| 95 parameters == null ? {} : new Map.from(parameters)); | 78 parameters == null ? {} : new Map.from(parameters)); |
| 96 | 79 |
| 97 /// Returns a copy of this [MediaType] with some fields altered. | 80 /// Returns a copy of this [MediaType] with some fields altered. |
| 98 /// | 81 /// |
| 99 /// [type] and [subtype] alter the corresponding fields. [mimeType] is parsed | 82 /// [type] and [subtype] alter the corresponding fields. [mimeType] is parsed |
| 100 /// and alters both the [type] and [subtype] fields; it cannot be passed along | 83 /// and alters both the [type] and [subtype] fields; it cannot be passed along |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 139 /// | 122 /// |
| 140 /// This will produce a valid HTTP media type. | 123 /// This will produce a valid HTTP media type. |
| 141 String toString() { | 124 String toString() { |
| 142 var buffer = new StringBuffer() | 125 var buffer = new StringBuffer() |
| 143 ..write(type) | 126 ..write(type) |
| 144 ..write("/") | 127 ..write("/") |
| 145 ..write(subtype); | 128 ..write(subtype); |
| 146 | 129 |
| 147 parameters.forEach((attribute, value) { | 130 parameters.forEach((attribute, value) { |
| 148 buffer.write("; $attribute="); | 131 buffer.write("; $attribute="); |
| 149 if (_nonToken.hasMatch(value)) { | 132 if (nonToken.hasMatch(value)) { |
| 150 buffer | 133 buffer |
| 151 ..write('"') | 134 ..write('"') |
| 152 ..write( | 135 ..write( |
| 153 value.replaceAllMapped(_escapedChar, (match) => "\\" + match[0])) | 136 value.replaceAllMapped(_escapedChar, (match) => "\\" + match[0])) |
| 154 ..write('"'); | 137 ..write('"'); |
| 155 } else { | 138 } else { |
| 156 buffer.write(value); | 139 buffer.write(value); |
| 157 } | 140 } |
| 158 }); | 141 }); |
| 159 | 142 |
| 160 return buffer.toString(); | 143 return buffer.toString(); |
| 161 } | 144 } |
| 162 } | 145 } |
| OLD | NEW |