contentTypeChecker.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. var debug = require('debug')('ylt:contentTypeChecker');
  2. var Q = require('q');
  3. var FileType = require('file-type');
  4. var isSvg = require('is-svg');
  5. var isJson = require('is-json');
  6. var ContentTypeChecker = function() {
  7. async function checkContentType(entry) {
  8. var deferred = Q.defer();
  9. // Setting isSomething values:
  10. switch(entry.type) {
  11. case 'html':
  12. entry.isHTML = true;
  13. break;
  14. case 'xml':
  15. entry.isXML = true;
  16. break;
  17. case 'css':
  18. entry.isCSS = true;
  19. break;
  20. case 'js':
  21. entry.isJS = true;
  22. break;
  23. case 'json':
  24. entry.isJSON = true;
  25. break;
  26. case 'image':
  27. entry.isImage = true;
  28. break;
  29. case 'webfont':
  30. entry.isWebFont = true;
  31. break;
  32. case 'video':
  33. entry.isVideo = true;
  34. break;
  35. case 'favicon':
  36. entry.isFavicon = true;
  37. break;
  38. }
  39. // Now let's check for mistakes by analysing body content. It happens more often then we think!
  40. // Ignore very small files as they are generally tracking pixels
  41. if (entry.weightCheck && entry.weightCheck.bodyBuffer && entry.weightCheck.bodySize > 100) {
  42. var foundType;
  43. try {
  44. foundType = await findContentType(entry.weightCheck.bodyBuffer);
  45. // If it's an image or a font, then rewrite.
  46. if (foundType !== null && (foundType.type === 'image' || foundType.type === 'webfont' || foundType.type === 'json')) {
  47. if (foundType.type !== entry.type) {
  48. debug('Content type %s is wrong for %s. It should be %s.', entry.type, entry.url, foundType.type);
  49. }
  50. rewriteContentType(entry, foundType);
  51. }
  52. } catch(err) {
  53. debug('Error while analyzing the contentType of %s', entry.url);
  54. debug(err);
  55. }
  56. }
  57. deferred.resolve(entry);
  58. return deferred.promise;
  59. }
  60. async function findContentType(bodyBuffer) {
  61. var bodyStr = bodyBuffer.toString();
  62. // https://github.com/sindresorhus/is-svg/issues/7
  63. if (/<svg/.test(bodyStr) && isSvg(bodyStr)) {
  64. return contentTypes.svg;
  65. }
  66. if (isJson(bodyStr)) {
  67. return contentTypes.json;
  68. }
  69. const type = await FileType.fromBuffer(bodyBuffer);
  70. if (type && type.ext && contentTypes[type.ext]) {
  71. return contentTypes[type.ext];
  72. }
  73. return null;
  74. }
  75. function rewriteContentType(entry, contentTypeObj) {
  76. delete(entry.isHTML);
  77. delete(entry.isXML);
  78. delete(entry.isCSS);
  79. delete(entry.isJS);
  80. delete(entry.isJSON);
  81. delete(entry.isImage);
  82. delete(entry.isSVG);
  83. delete(entry.isVideo);
  84. delete(entry.isWebFont);
  85. delete(entry.isTTF);
  86. delete(entry.isFavicon);
  87. entry.contentType = contentTypeObj.mimes[0];
  88. contentTypeObj.updateFn(entry);
  89. }
  90. var contentTypes = {
  91. jpg: {
  92. type: 'image',
  93. mimes: ['image/jpeg'],
  94. updateFn: function(entry) {
  95. entry.type = 'image';
  96. entry.isImage = true;
  97. }
  98. },
  99. png: {
  100. type: 'image',
  101. mimes: ['image/png'],
  102. updateFn: function(entry) {
  103. entry.type = 'image';
  104. entry.isImage = true;
  105. }
  106. },
  107. svg: {
  108. type: 'image',
  109. mimes: ['image/svg+xml'],
  110. updateFn: function(entry) {
  111. entry.type = 'image';
  112. entry.isImage = true;
  113. entry.isSVG = true;
  114. }
  115. },
  116. gif: {
  117. type: 'image',
  118. mimes: ['image/gif'],
  119. updateFn: function(entry) {
  120. entry.type = 'image';
  121. entry.isImage = true;
  122. }
  123. },
  124. webp: {
  125. type: 'image',
  126. mimes: ['image/webp'],
  127. updateFn: function(entry) {
  128. entry.type = 'image';
  129. entry.isImage = true;
  130. }
  131. },
  132. avif: {
  133. type: 'image',
  134. mimes: ['image/avif'],
  135. updateFn: function(entry) {
  136. entry.type = 'image';
  137. entry.isImage = true;
  138. }
  139. },
  140. woff: {
  141. type: 'webfont',
  142. mimes: ['application/x-font-woff', 'application/font-woff', 'font/woff'],
  143. updateFn: function(entry) {
  144. entry.type = 'webfont';
  145. entry.isWebFont = true;
  146. entry.isWoff = true;
  147. }
  148. },
  149. woff2: {
  150. type: 'webfont',
  151. mimes: ['font/woff2', 'application/x-font-woff2', 'application/font-woff2'],
  152. updateFn: function(entry) {
  153. entry.type = 'webfont';
  154. entry.isWebFont = true;
  155. entry.isWoff2 = true;
  156. }
  157. },
  158. otf: {
  159. type: 'webfont',
  160. mimes: ['application/x-font-otf', 'font/otf', 'font/opentype', 'application/x-font-opentype'],
  161. updateFn: function(entry) {
  162. entry.type = 'webfont';
  163. entry.isWebFont = true;
  164. }
  165. },
  166. ttf: {
  167. type: 'webfont',
  168. mimes: ['application/x-font-ttf', 'font/ttf', 'application/x-font-truetype'],
  169. updateFn: function(entry) {
  170. entry.type = 'webfont';
  171. entry.isWebFont = true;
  172. entry.isTTF = true;
  173. }
  174. },
  175. eot: {
  176. type: 'webfont',
  177. mimes: ['application/vnd.ms-fontobject', 'font/eot'],
  178. updateFn: function(entry) {
  179. entry.type = 'webfont';
  180. entry.isWebFont = true;
  181. }
  182. },
  183. json: {
  184. type: 'json',
  185. mimes: ['application/json'],
  186. updateFn: function(entry) {
  187. entry.type = 'json';
  188. entry.isJSON = true;
  189. }
  190. },
  191. };
  192. return {
  193. checkContentType: checkContentType,
  194. findContentType: findContentType
  195. };
  196. };
  197. module.exports = new ContentTypeChecker();