| // Copyright (c) 2014, 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. |
| |
| import 'package:collection/collection.dart'; |
| import 'package:string_scanner/string_scanner.dart'; |
| |
| import 'case_insensitive_map.dart'; |
| import 'scan.dart'; |
| import 'utils.dart'; |
| |
| /// A regular expression matching a character that needs to be backslash-escaped |
| /// in a quoted string. |
| final _escapedChar = new RegExp(r'["\x00-\x1F\x7F]'); |
| |
| /// A class representing an HTTP media type, as used in Accept and Content-Type |
| /// headers. |
| /// |
| /// This is immutable; new instances can be created based on an old instance by |
| /// calling [change]. |
| class MediaType { |
| /// The primary identifier of the MIME type. |
| /// |
| /// This is always lowercase. |
| final String type; |
| |
| /// The secondary identifier of the MIME type. |
| /// |
| /// This is always lowercase. |
| final String subtype; |
| |
| /// The parameters to the media type. |
| /// |
| /// This map is immutable and the keys are case-insensitive. |
| final Map<String, String> parameters; |
| |
| /// The media type's MIME type. |
| String get mimeType => "$type/$subtype"; |
| |
| /// Parses a media type. |
| /// |
| /// This will throw a FormatError if the media type is invalid. |
| factory MediaType.parse(String mediaType) { |
| // This parsing is based on sections 3.6 and 3.7 of the HTTP spec: |
| // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html. |
| return wrapFormatException("media type", mediaType, () { |
| var scanner = new StringScanner(mediaType); |
| scanner.scan(whitespace); |
| scanner.expect(token); |
| var type = scanner.lastMatch[0]; |
| scanner.expect('/'); |
| scanner.expect(token); |
| var subtype = scanner.lastMatch[0]; |
| scanner.scan(whitespace); |
| |
| var parameters = <String, String>{}; |
| while (scanner.scan(';')) { |
| scanner.scan(whitespace); |
| scanner.expect(token); |
| var attribute = scanner.lastMatch[0]; |
| scanner.expect('='); |
| |
| var value; |
| if (scanner.scan(token)) { |
| value = scanner.lastMatch[0]; |
| } else { |
| value = expectQuotedString(scanner); |
| } |
| |
| scanner.scan(whitespace); |
| parameters[attribute] = value; |
| } |
| |
| scanner.expectDone(); |
| return new MediaType(type, subtype, parameters); |
| }); |
| } |
| |
| MediaType(String type, String subtype, [Map<String, String> parameters]) |
| : type = type.toLowerCase(), |
| subtype = subtype.toLowerCase(), |
| parameters = new UnmodifiableMapView( |
| parameters == null ? {} : new CaseInsensitiveMap.from(parameters)); |
| |
| /// Returns a copy of this [MediaType] with some fields altered. |
| /// |
| /// [type] and [subtype] alter the corresponding fields. [mimeType] is parsed |
| /// and alters both the [type] and [subtype] fields; it cannot be passed along |
| /// with [type] or [subtype]. |
| /// |
| /// [parameters] overwrites and adds to the corresponding field. If |
| /// [clearParameters] is passed, it replaces the corresponding field entirely |
| /// instead. |
| MediaType change( |
| {String type, |
| String subtype, |
| String mimeType, |
| Map<String, String> parameters, |
| bool clearParameters: false}) { |
| if (mimeType != null) { |
| if (type != null) { |
| throw new ArgumentError("You may not pass both [type] and [mimeType]."); |
| } else if (subtype != null) { |
| throw new ArgumentError("You may not pass both [subtype] and " |
| "[mimeType]."); |
| } |
| |
| var segments = mimeType.split('/'); |
| if (segments.length != 2) { |
| throw new FormatException('Invalid mime type "$mimeType".'); |
| } |
| |
| type = segments[0]; |
| subtype = segments[1]; |
| } |
| |
| if (type == null) type = this.type; |
| if (subtype == null) subtype = this.subtype; |
| if (parameters == null) parameters = {}; |
| |
| if (!clearParameters) { |
| var newParameters = parameters; |
| parameters = new Map.from(this.parameters); |
| parameters.addAll(newParameters); |
| } |
| |
| return new MediaType(type, subtype, parameters); |
| } |
| |
| /// Converts the media type to a string. |
| /// |
| /// This will produce a valid HTTP media type. |
| String toString() { |
| var buffer = new StringBuffer()..write(type)..write("/")..write(subtype); |
| |
| parameters.forEach((attribute, value) { |
| buffer.write("; $attribute="); |
| if (nonToken.hasMatch(value)) { |
| buffer |
| ..write('"') |
| ..write( |
| value.replaceAllMapped(_escapedChar, (match) => "\\" + match[0])) |
| ..write('"'); |
| } else { |
| buffer.write(value); |
| } |
| }); |
| |
| return buffer.toString(); |
| } |
| } |