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 |