mime.js 7.3 KB

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