mime.ts 6.3 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7 8 9
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

import paths = require('vs/base/common/paths');
import types = require('vs/base/common/types');
import strings = require('vs/base/common/strings');
10
import {match} from 'vs/base/common/glob';
E
Erich Gamma 已提交
11 12 13 14 15

export let MIME_TEXT = 'text/plain';
export let MIME_BINARY = 'application/octet-stream';
export let MIME_UNKNOWN = 'application/unknown';

16
export interface ITextMimeAssociation {
17 18 19 20 21
	mime: string;
	filename?: string;
	extension?: string;
	filepattern?: string;
	firstline?: RegExp;
22 23 24
	userConfigured?: boolean;
}

25
let registeredAssociations: ITextMimeAssociation[] = [];
E
Erich Gamma 已提交
26

27
/**
28
 * Associate a text mime to the registry.
29
 */
30
export function registerTextMime(association: ITextMimeAssociation): void {
E
Erich Gamma 已提交
31

32 33
	// Register
	registeredAssociations.push(association);
E
Erich Gamma 已提交
34

35 36 37 38 39 40
	// Check for conflicts unless this is a user configured association
	if (!association.userConfigured) {
		registeredAssociations.forEach(a => {
			if (a.mime === association.mime || a.userConfigured) {
				return; // same mime or userConfigured is ok
			}
E
Erich Gamma 已提交
41

42 43
			if (association.extension && a.extension === association.extension) {
				console.warn(`Overwriting extension <<${association.extension}>> to now point to mime <<${association.mime}>>`);
44
			}
45 46 47 48 49 50 51 52 53 54 55 56 57

			if (association.filename && a.filename === association.filename) {
				console.warn(`Overwriting filename <<${association.filename}>> to now point to mime <<${association.mime}>>`);
			}

			if (association.filepattern && a.filepattern === association.filepattern) {
				console.warn(`Overwriting filepattern <<${association.filepattern}>> to now point to mime <<${association.mime}>>`);
			}

			if (association.firstline && a.firstline === association.firstline) {
				console.warn(`Overwriting firstline <<${association.firstline}>> to now point to mime <<${association.mime}>>`);
			}
		});
E
Erich Gamma 已提交
58 59 60
	}
}

61 62 63 64 65 66 67 68 69 70 71
/**
 * Clear text mimes from the registry.
 */
export function clearTextMimes(onlyUserConfigured?: boolean): void {
	if (!onlyUserConfigured) {
		registeredAssociations = [];
	} else {
		registeredAssociations = registeredAssociations.filter(a => !a.userConfigured);
	}
}

E
Erich Gamma 已提交
72
/**
73
 * Given a file, return the best matching mime type for it
E
Erich Gamma 已提交
74 75 76 77 78 79
 */
export function guessMimeTypes(path: string, firstLine?: string): string[] {
	if (!path) {
		return [MIME_UNKNOWN];
	}

80
	path = path.toLowerCase();
E
Erich Gamma 已提交
81

82 83
	// 1.) User configured mappings have highest priority
	let configuredMime = guessMimeTypeByPath(path, registeredAssociations.filter(a => a.userConfigured));
84 85 86
	if (configuredMime) {
		return [configuredMime, MIME_TEXT];
	}
E
Erich Gamma 已提交
87

88 89 90 91 92 93 94
	// 2.) Registered mappings have middle priority
	let registeredMime = guessMimeTypeByPath(path, registeredAssociations.filter(a => !a.userConfigured));
	if (registeredMime) {
		return [registeredMime, MIME_TEXT];
	}

	// 3.) Firstline has lowest priority
95 96 97 98
	if (firstLine) {
		let firstlineMime = guessMimeTypeByFirstline(firstLine);
		if (firstlineMime) {
			return [firstlineMime, MIME_TEXT];
E
Erich Gamma 已提交
99 100 101
		}
	}

102 103 104
	return [MIME_UNKNOWN];
}

105
function guessMimeTypeByPath(path: string, associations: ITextMimeAssociation[]): string {
B
Benjamin Pasero 已提交
106
	let filename = paths.basename(path);
107 108 109 110

	let filenameMatch: ITextMimeAssociation;
	let patternMatch: ITextMimeAssociation;
	let extensionMatch: ITextMimeAssociation;
E
Erich Gamma 已提交
111

112 113
	for (var i = 0; i < associations.length; i++) {
		let association = associations[i];
E
Erich Gamma 已提交
114 115

		// First exact name match
116 117
		if (association.filename && filename === association.filename.toLowerCase()) {
			filenameMatch = association;
E
Erich Gamma 已提交
118 119 120
			break; // take it!
		}

121
		// Longest pattern match
122 123 124 125 126 127
		if (association.filepattern) {
			let target = association.filepattern.indexOf(paths.sep) >= 0 ? path : filename; // match on full path if pattern contains path separator
			if (match(association.filepattern.toLowerCase(), target)) {
				if (!patternMatch || association.filepattern.length > patternMatch.filepattern.length) {
					patternMatch = association;
				}
128 129 130
			}
		}

131
		// Longest extension match
132
		if (association.extension) {
B
Benjamin Pasero 已提交
133
			if (strings.endsWith(filename, association.extension.toLowerCase())) {
134 135 136
				if (!extensionMatch || association.extension.length > extensionMatch.extension.length) {
					extensionMatch = association;
				}
E
Erich Gamma 已提交
137 138 139 140
			}
		}
	}

141 142 143
	// 1.) Exact name match has second highest prio
	if (filenameMatch) {
		return filenameMatch.mime;
E
Erich Gamma 已提交
144 145
	}

146 147 148
	// 2.) Match on pattern
	if (patternMatch) {
		return patternMatch.mime;
E
Erich Gamma 已提交
149 150
	}

151
	// 3.) Match on extension comes next
152
	if (extensionMatch) {
153
		return extensionMatch.mime;
154 155
	}

156 157 158 159 160 161 162 163 164
	return null;
}

function guessMimeTypeByFirstline(firstLine: string): string {
	if (strings.startsWithUTF8BOM(firstLine)) {
		firstLine = firstLine.substr(1);
	}

	if (firstLine.length > 0) {
165 166 167 168 169
		for (let i = 0; i < registeredAssociations.length; ++i) {
			let association = registeredAssociations[i];
			if (!association.firstline) {
				continue;
			}
170 171

			// Make sure the entire line matches, not just a subpart.
172
			let matches = firstLine.match(association.firstline);
173
			if (matches && matches.length > 0 && matches[0].length === firstLine.length) {
174
				return association.mime;
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
			}
		}
	}

	return null;
}

export function isBinaryMime(mimes: string): boolean;
export function isBinaryMime(mimes: string[]): boolean;
export function isBinaryMime(mimes: any): boolean {
	if (!mimes) {
		return false;
	}

	let mimeVals: string[];
	if (types.isArray(mimes)) {
		mimeVals = (<string[]>mimes);
	} else {
		mimeVals = (<string>mimes).split(',').map((mime) => mime.trim());
	}

	return mimeVals.indexOf(MIME_BINARY) >= 0;
E
Erich Gamma 已提交
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
}

export function isUnspecific(mime: string[] | string): boolean {
	if (!mime) {
		return true;
	}

	if (typeof mime === 'string') {
		return mime === MIME_BINARY || mime === MIME_TEXT || mime === MIME_UNKNOWN;
	}

	return mime.length === 1 && isUnspecific(mime[0]);
}

export function suggestFilename(theMime: string, prefix: string): string {
212 213 214 215 216 217 218 219
	for (var i = 0; i < registeredAssociations.length; i++) {
		let association = registeredAssociations[i];
		if (association.userConfigured) {
			continue; // only support registered ones
		}

		if (association.mime === theMime && association.extension) {
			return prefix + association.extension;
E
Erich Gamma 已提交
220 221 222 223 224
		}
	}

	return null;
}