Skip to content
Snippets Groups Projects
Commit cc74cc33 authored by ajohnsen@google.com's avatar ajohnsen@google.com
Browse files

Add a new MIME-type package 'mime', with implementation for handling mime-type...

Add a new MIME-type package 'mime', with implementation for handling mime-type lookup by path (finename extension) and magic-number matching.

BUG=
R=sgjesse@google.com

Review URL: https://codereview.chromium.org//17582009

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/mime@24324 260f80e4-7a28-3924-810f-c04153c831b5
parents
No related branches found
No related tags found
No related merge requests found
// Copyright (c) 2013, 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 mime;
part 'src/mime_type.dart';
part 'src/extension_map.dart';
part 'src/magic_number.dart';
This diff is collapsed.
// Copyright (c) 2013, 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.
part of mime;
class _MagicNumber {
final String mimeType;
final List<int> numbers;
final List<int> mask;
const _MagicNumber(this.mimeType, this.numbers, {this.mask});
bool matches(List<int> header) {
if (header.length < numbers.length) return false;
for (int i = 0; i < numbers.length; i++) {
if (mask != null) {
if ((mask[i] & numbers[i]) != (mask[i] & header[i])) return false;
} else {
if (numbers[i] != header[i]) return false;
}
}
return true;
}
}
int _defaultMagicNumbersMaxLength = 16;
List<_MagicNumber> _defaultMagicNumbers = const [
const _MagicNumber('application/pdf', const [0x25, 0x50, 0x44, 0x46]),
const _MagicNumber('application/postscript', const [0x25, 0x51]),
const _MagicNumber('image/gif', const [0x47, 0x49, 0x46, 0x38, 0x37, 0x61]),
const _MagicNumber('image/gif', const [0x47, 0x49, 0x46, 0x38, 0x39, 0x61]),
const _MagicNumber('image/jpeg', const [0xFF, 0xD8]),
const _MagicNumber('image/png',
const [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]),
const _MagicNumber('image/tiff', const [0x49, 0x49, 0x2A, 0x00]),
const _MagicNumber('image/tiff', const [0x4D, 0x4D, 0x00, 0x2A]),
const _MagicNumber('video/mp4',
const [0x00, 0x00, 0x00, 0x00, 0x66, 0x74,
0x79, 0x70, 0x33, 0x67, 0x70, 0x35],
mask: const [0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
];
// Copyright (c) 2013, 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.
part of mime_type;
MimeTypeResolver _globalResolver = new MimeTypeResolver();
/**
* The maximum number of bytes needed, to match all default magic-numbers.
*/
int get defaultMagicNumbersMaxLength => _defaultMagicNumbersMaxLength;
/**
* Extract the extension from [path] and use that for MIME-type lookup, using
* the default extension map.
*
* If no matching MIME-type was found, `null` is returned.
*
* If [headerBytes] is present, a match for known magic-numbers will be
* performed first. This allows the correct mime-type to be found, even though
* a file have been saved using the wrong file-name extension. If less than
* [defaultMagicNumbersMaxLength] bytes was provided, some magic-numbers won't
* be matched against.
*/
String lookupMimeType(String path,
{List<int> headerBytes})
=> _globalResolver.lookup(path, headerBytes: headerBytes);
/**
* MIME-type resolver class, used to customize the lookup of mime-types.
*/
class MimeTypeResolver {
final Map<String, String> _extensionMap = {};
final List<_MagicNumber> _magicNumbers = [];
bool _useDefault;
int _magicNumbersMaxLength;
/**
* Create a new empty [MimeTypeResolver].
*/
MimeTypeResolver.empty() : _useDefault = false, _magicNumbersMaxLength = 0;
/**
* Create a new [MimeTypeResolver] containing the default scope.
*/
MimeTypeResolver() :
_useDefault = true,
_magicNumbersMaxLength = _defaultMagicNumbersMaxLength;
/**
* Get the maximum number of bytes required to match all magic numbers, when
* performing [lookup] with headerBytes present.
*/
int get magicNumbersMaxLength => _magicNumbersMaxLength;
/**
* Extract the extension from [path] and use that for MIME-type lookup.
*
* If no matching MIME-type was found, `null` is returned.
*
* If [headerBytes] is present, a match for known magic-numbers will be
* performed first. This allows the correct mime-type to be found, even though
* a file have been saved using the wrong file-name extension. If less than
* [magicNumbersMaxLength] bytes was provided, some magic-numbers won't
* be matched against.
*/
String lookup(String path,
{List<int> headerBytes}) {
String result;
if (headerBytes != null) {
result =_matchMagic(headerBytes, _magicNumbers);
if (result != null) return result;
if (_useDefault) {
result =_matchMagic(headerBytes, _defaultMagicNumbers);
if (result != null) return result;
}
}
var ext = _ext(path);
result = _extensionMap[ext];
if (result != null) return result;
if (_useDefault) {
result = _defaultExtensionMap[ext];
if (result != null) return result;
}
return null;
}
/**
* Add a new MIME-type mapping to the [MimeTypeResolver]. If the [extension]
* is already present in the [MimeTypeResolver], it'll be overwritten.
*/
void addExtension(String extension, String mimeType) {
_extensionMap[extension] = mimeType;
}
/**
* Add a new magic-number mapping to the [MimeTypeResolver].
*
* If [mask] is present,the [mask] is used to only perform matching on
* selective bits. The [mask] must have the same length as [bytes].
*/
void addMagicNumber(List<int> bytes, String mimeType, {List<int> mask}) {
if (mask != null && bytes.length != mask.length) {
throw new ArgumentError('Bytes and mask are of different lengths');
}
if (bytes.length > _magicNumbersMaxLength) {
_magicNumbersMaxLength = bytes.length;
}
_magicNumbers.add(new _MagicNumber(mimeType, bytes, mask: mask));
}
static String _matchMagic(List<int> headerBytes,
List<_MagicNumber> magicNumbers) {
for (var mn in magicNumbers) {
if (mn.matches(headerBytes)) return mn.mimeType;
}
return null;
}
static String _ext(String path) {
int index = path.lastIndexOf('.');
if (index < 0 || index + 1 >= path.length) return path;
return path.substring(index + 1).toLowerCase();
}
}
name: mime
author: "Dart Team <misc@dartlang.org>"
homepage: http://www.dartlang.org
description: >
Helper-package for working with MIME.
// Copyright (c) 2013, 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:unittest/unittest.dart";
import 'package:mime/mime.dart';
void expectMimeType(String path,
String expectedMimeType,
{List<int> headerBytes,
MimeTypeResolver resolver}) {
String mimeType;
if (resolver == null) {
mimeType = lookupMimeType(path, headerBytes: headerBytes);
} else {
mimeType = resolver.lookup(path, headerBytes: headerBytes);
}
if (mimeType != expectedMimeType) {
throw "Expect '$expectedMimeType' but got '$mimeType'";
}
}
void main() {
group('global-lookup', () {
test('by-path', () {
expectMimeType('file.dart', 'application/dart');
// Test mixed-case
expectMimeType('file.DaRT', 'application/dart');
expectMimeType('file.html', 'text/html');
expectMimeType('file.xhtml', 'application/xhtml+xml');
expectMimeType('file.jpeg', 'image/jpeg');
expectMimeType('file.jpg', 'image/jpeg');
expectMimeType('file.png', 'image/png');
expectMimeType('file.gif', 'image/gif');
expectMimeType('file.cc', 'text/x-c');
expectMimeType('file.c', 'text/x-c');
expectMimeType('file.css', 'text/css');
expectMimeType('file.js', 'application/javascript');
expectMimeType('file.ps', 'application/postscript');
expectMimeType('file.pdf', 'application/pdf');
expectMimeType('file.tiff', 'image/tiff');
expectMimeType('file.tif', 'image/tiff');
});
test('unknown-mime-type', () {
expectMimeType('file.unsupported-extension', null);
});
test('by-header-bytes', () {
expectMimeType('file.jpg',
'image/png',
headerBytes: [0x89, 0x50, 0x4E, 0x47,
0x0D, 0x0A, 0x1A, 0x0A]);
expectMimeType('file.jpg',
'image/gif',
headerBytes: [0x47, 0x49, 0x46, 0x38, 0x39,
0x61, 0x0D, 0x0A, 0x1A, 0x0A]);
expectMimeType('file.gif',
'image/jpeg',
headerBytes: [0xFF, 0xD8, 0x46, 0x38, 0x39,
0x61, 0x0D, 0x0A, 0x1A, 0x0A]);
expectMimeType('file.mp4',
'video/mp4',
headerBytes: [0x00, 0x00, 0x00, 0x04, 0x66, 0x74,
0x79, 0x70, 0x33, 0x67, 0x70, 0x35]);
});
});
group('custom-resolver', () {
test('override-extension', () {
var resolver = new MimeTypeResolver();
resolver.addExtension('jpg', 'my-mime-type');
expectMimeType('file.jpg', 'my-mime-type', resolver: resolver);
});
test('fallthrough-extension', () {
var resolver = new MimeTypeResolver();
resolver.addExtension('jpg2', 'my-mime-type');
expectMimeType('file.jpg', 'image/jpeg', resolver: resolver);
});
test('with-mask', () {
var resolver = new MimeTypeResolver.empty();
resolver.addMagicNumber([0x01, 0x02, 0x03],
'my-mime-type',
mask: [0x01, 0xFF, 0xFE]);
expectMimeType('file',
'my-mime-type',
headerBytes: [0x01, 0x02, 0x03],
resolver: resolver);
expectMimeType('file',
null,
headerBytes: [0x01, 0x03, 0x03],
resolver: resolver);
expectMimeType('file',
'my-mime-type',
headerBytes: [0xFF, 0x02, 0x02],
resolver: resolver);
});
});
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment