| Index: pkg/shelf/lib/src/media_type.dart
|
| diff --git a/pkg/shelf/lib/src/media_type.dart b/pkg/shelf/lib/src/media_type.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..595fc9aca3f2affaad41a7eaa6aab2725a5b2922
|
| --- /dev/null
|
| +++ b/pkg/shelf/lib/src/media_type.dart
|
| @@ -0,0 +1,156 @@
|
| +// 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.
|
| +
|
| +library shelf.media_type;
|
| +
|
| +import 'package:collection/collection.dart';
|
| +
|
| +import 'string_scanner.dart';
|
| +
|
| +// All of the following regular expressions come from section 2.2 of the HTTP
|
| +// spec: http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html
|
| +final _lws = new RegExp(r"(?:\r\n)?[ \t]+");
|
| +final _token = new RegExp(r'[^()<>@,;:"\\/[\]?={} \t\x00-\x1F\x7F]+');
|
| +final _quotedString = new RegExp(r'"(?:[^"\x00-\x1F\x7F]|\\.)*"');
|
| +final _quotedPair = new RegExp(r'\\(.)');
|
| +
|
| +/// A regular expression matching any number of [_lws] productions in a row.
|
| +final _whitespace = new RegExp("(?:${_lws.pattern})*");
|
| +
|
| +/// A regular expression matching a character that is not a valid HTTP token.
|
| +final _nonToken = new RegExp(r'[()<>@,;:"\\/\[\]?={} \t\x00-\x1F\x7F]');
|
| +
|
| +/// 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.
|
| +class MediaType {
|
| + /// The primary identifier of the MIME type.
|
| + final String type;
|
| +
|
| + /// The secondary identifier of the MIME type.
|
| + final String subtype;
|
| +
|
| + /// The parameters to the media type.
|
| + ///
|
| + /// This map is immutable.
|
| + 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.
|
| + var errorMessage = 'Invalid media type "$mediaType".';
|
| + var scanner = new StringScanner(mediaType);
|
| + scanner.scan(_whitespace);
|
| + scanner.expect(_token, errorMessage);
|
| + var type = scanner.lastMatch[0];
|
| + scanner.expect('/', errorMessage);
|
| + scanner.expect(_token, errorMessage);
|
| + var subtype = scanner.lastMatch[0];
|
| + scanner.scan(_whitespace);
|
| +
|
| + var parameters = {};
|
| + while (scanner.scan(';')) {
|
| + scanner.scan(_whitespace);
|
| + scanner.expect(_token, errorMessage);
|
| + var attribute = scanner.lastMatch[0];
|
| + scanner.expect('=', errorMessage);
|
| +
|
| + var value;
|
| + if (scanner.scan(_token)) {
|
| + value = scanner.lastMatch[0];
|
| + } else {
|
| + scanner.expect(_quotedString, errorMessage);
|
| + var quotedString = scanner.lastMatch[0];
|
| + value = quotedString.substring(1, quotedString.length - 1).
|
| + replaceAllMapped(_quotedPair, (match) => match[1]);
|
| + }
|
| +
|
| + scanner.scan(_whitespace);
|
| + parameters[attribute] = value;
|
| + }
|
| +
|
| + if (!scanner.isDone) throw new FormatException(errorMessage);
|
| +
|
| + return new MediaType(type, subtype, parameters);
|
| + }
|
| +
|
| + MediaType(this.type, this.subtype, [Map<String, String> parameters])
|
| + : this.parameters = new UnmodifiableMapView(
|
| + parameters == null ? {} : new Map.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();
|
| + }
|
| +}
|
|
|