mime.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. import { parse } from './glob.js';
  6. import { Schemas } from './network.js';
  7. import { basename, posix } from './path.js';
  8. import { DataUri } from './resources.js';
  9. import { startsWithUTF8BOM } from './strings.js';
  10. export var Mimes;
  11. (function (Mimes) {
  12. Mimes.text = 'text/plain';
  13. Mimes.binary = 'application/octet-stream';
  14. Mimes.unknown = 'application/unknown';
  15. Mimes.markdown = 'text/markdown';
  16. Mimes.latex = 'text/latex';
  17. })(Mimes || (Mimes = {}));
  18. let registeredAssociations = [];
  19. let nonUserRegisteredAssociations = [];
  20. let userRegisteredAssociations = [];
  21. /**
  22. * Associate a text mime to the registry.
  23. */
  24. export function registerTextMime(association, warnOnOverwrite = false) {
  25. // Register
  26. const associationItem = toTextMimeAssociationItem(association);
  27. registeredAssociations.push(associationItem);
  28. if (!associationItem.userConfigured) {
  29. nonUserRegisteredAssociations.push(associationItem);
  30. }
  31. else {
  32. userRegisteredAssociations.push(associationItem);
  33. }
  34. // Check for conflicts unless this is a user configured association
  35. if (warnOnOverwrite && !associationItem.userConfigured) {
  36. registeredAssociations.forEach(a => {
  37. if (a.mime === associationItem.mime || a.userConfigured) {
  38. return; // same mime or userConfigured is ok
  39. }
  40. if (associationItem.extension && a.extension === associationItem.extension) {
  41. console.warn(`Overwriting extension <<${associationItem.extension}>> to now point to mime <<${associationItem.mime}>>`);
  42. }
  43. if (associationItem.filename && a.filename === associationItem.filename) {
  44. console.warn(`Overwriting filename <<${associationItem.filename}>> to now point to mime <<${associationItem.mime}>>`);
  45. }
  46. if (associationItem.filepattern && a.filepattern === associationItem.filepattern) {
  47. console.warn(`Overwriting filepattern <<${associationItem.filepattern}>> to now point to mime <<${associationItem.mime}>>`);
  48. }
  49. if (associationItem.firstline && a.firstline === associationItem.firstline) {
  50. console.warn(`Overwriting firstline <<${associationItem.firstline}>> to now point to mime <<${associationItem.mime}>>`);
  51. }
  52. });
  53. }
  54. }
  55. function toTextMimeAssociationItem(association) {
  56. return {
  57. id: association.id,
  58. mime: association.mime,
  59. filename: association.filename,
  60. extension: association.extension,
  61. filepattern: association.filepattern,
  62. firstline: association.firstline,
  63. userConfigured: association.userConfigured,
  64. filenameLowercase: association.filename ? association.filename.toLowerCase() : undefined,
  65. extensionLowercase: association.extension ? association.extension.toLowerCase() : undefined,
  66. filepatternLowercase: association.filepattern ? parse(association.filepattern.toLowerCase()) : undefined,
  67. filepatternOnPath: association.filepattern ? association.filepattern.indexOf(posix.sep) >= 0 : false
  68. };
  69. }
  70. /**
  71. * Clear text mimes from the registry.
  72. */
  73. export function clearTextMimes(onlyUserConfigured) {
  74. if (!onlyUserConfigured) {
  75. registeredAssociations = [];
  76. nonUserRegisteredAssociations = [];
  77. userRegisteredAssociations = [];
  78. }
  79. else {
  80. registeredAssociations = registeredAssociations.filter(a => !a.userConfigured);
  81. userRegisteredAssociations = [];
  82. }
  83. }
  84. /**
  85. * Given a file, return the best matching mime type for it
  86. */
  87. export function guessMimeTypes(resource, firstLine) {
  88. let path;
  89. if (resource) {
  90. switch (resource.scheme) {
  91. case Schemas.file:
  92. path = resource.fsPath;
  93. break;
  94. case Schemas.data:
  95. const metadata = DataUri.parseMetaData(resource);
  96. path = metadata.get(DataUri.META_DATA_LABEL);
  97. break;
  98. default:
  99. path = resource.path;
  100. }
  101. }
  102. if (!path) {
  103. return [Mimes.unknown];
  104. }
  105. path = path.toLowerCase();
  106. const filename = basename(path);
  107. // 1.) User configured mappings have highest priority
  108. const configuredMime = guessMimeTypeByPath(path, filename, userRegisteredAssociations);
  109. if (configuredMime) {
  110. return [configuredMime, Mimes.text];
  111. }
  112. // 2.) Registered mappings have middle priority
  113. const registeredMime = guessMimeTypeByPath(path, filename, nonUserRegisteredAssociations);
  114. if (registeredMime) {
  115. return [registeredMime, Mimes.text];
  116. }
  117. // 3.) Firstline has lowest priority
  118. if (firstLine) {
  119. const firstlineMime = guessMimeTypeByFirstline(firstLine);
  120. if (firstlineMime) {
  121. return [firstlineMime, Mimes.text];
  122. }
  123. }
  124. return [Mimes.unknown];
  125. }
  126. function guessMimeTypeByPath(path, filename, associations) {
  127. var _a;
  128. let filenameMatch = null;
  129. let patternMatch = null;
  130. let extensionMatch = null;
  131. // We want to prioritize associations based on the order they are registered so that the last registered
  132. // association wins over all other. This is for https://github.com/microsoft/vscode/issues/20074
  133. for (let i = associations.length - 1; i >= 0; i--) {
  134. const association = associations[i];
  135. // First exact name match
  136. if (filename === association.filenameLowercase) {
  137. filenameMatch = association;
  138. break; // take it!
  139. }
  140. // Longest pattern match
  141. if (association.filepattern) {
  142. if (!patternMatch || association.filepattern.length > patternMatch.filepattern.length) {
  143. const target = association.filepatternOnPath ? path : filename; // match on full path if pattern contains path separator
  144. if ((_a = association.filepatternLowercase) === null || _a === void 0 ? void 0 : _a.call(association, target)) {
  145. patternMatch = association;
  146. }
  147. }
  148. }
  149. // Longest extension match
  150. if (association.extension) {
  151. if (!extensionMatch || association.extension.length > extensionMatch.extension.length) {
  152. if (filename.endsWith(association.extensionLowercase)) {
  153. extensionMatch = association;
  154. }
  155. }
  156. }
  157. }
  158. // 1.) Exact name match has second highest priority
  159. if (filenameMatch) {
  160. return filenameMatch.mime;
  161. }
  162. // 2.) Match on pattern
  163. if (patternMatch) {
  164. return patternMatch.mime;
  165. }
  166. // 3.) Match on extension comes next
  167. if (extensionMatch) {
  168. return extensionMatch.mime;
  169. }
  170. return null;
  171. }
  172. function guessMimeTypeByFirstline(firstLine) {
  173. if (startsWithUTF8BOM(firstLine)) {
  174. firstLine = firstLine.substr(1);
  175. }
  176. if (firstLine.length > 0) {
  177. // We want to prioritize associations based on the order they are registered so that the last registered
  178. // association wins over all other. This is for https://github.com/microsoft/vscode/issues/20074
  179. for (let i = registeredAssociations.length - 1; i >= 0; i--) {
  180. const association = registeredAssociations[i];
  181. if (!association.firstline) {
  182. continue;
  183. }
  184. const matches = firstLine.match(association.firstline);
  185. if (matches && matches.length > 0) {
  186. return association.mime;
  187. }
  188. }
  189. }
  190. return null;
  191. }