index.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. const fs = require('fs');
  2. const signatures = require('./signatures.json');
  3. class FileDetector {
  4. detectFile(filename) {
  5. return new Promise((resolve, reject) => {
  6. this.fromFile(filename, 2000, (err, result) => {
  7. if (err) reject(err);
  8. resolve(result);
  9. });
  10. });
  11. }
  12. //все, что ниже, взято здесь: https://github.com/dimapaloskin/detect-file-type
  13. fromFile(filePath, bufferLength, callback) {
  14. if (typeof bufferLength === 'function') {
  15. callback = bufferLength;
  16. bufferLength = undefined;
  17. }
  18. this.getFileSize(filePath, (err, fileSize) => {
  19. if (err) {
  20. return callback(err);
  21. }
  22. fs.open(filePath, 'r', (err, fd) => {
  23. if (err) {
  24. return callback(err);
  25. }
  26. let bufferSize = bufferLength;
  27. if (!bufferSize) {
  28. bufferSize = 500;
  29. }
  30. if (fileSize < bufferSize) {
  31. bufferSize = fileSize;
  32. }
  33. const buffer = Buffer.alloc(bufferSize);
  34. fs.read(fd, buffer, 0, bufferSize, 0, (err) => {
  35. fs.close(fd);
  36. if (err) {
  37. return callback(err);
  38. }
  39. this.fromBuffer(buffer, callback);
  40. });
  41. });
  42. });
  43. }
  44. fromBuffer(buffer, callback) {
  45. let result = null;
  46. const invalidSignaturesList = this.validateSigantures();
  47. if (invalidSignaturesList.length) {
  48. return callback(invalidSignaturesList);
  49. }
  50. signatures.every((signature) => {
  51. if (this.detect(buffer, signature.rules)) {
  52. result = {
  53. ext: signature.ext,
  54. mime: signature.mime
  55. };
  56. if (signature.iana)
  57. result.iana = signature.iana;
  58. return false;
  59. }
  60. return true;
  61. });
  62. callback(null, result);
  63. }
  64. detect(buffer, receivedRules, type) {
  65. if (!type) {
  66. type = 'and';
  67. }
  68. const rules = [...receivedRules];
  69. let isDetected = true;
  70. rules.every((rule) => {
  71. if (rule.type === 'equal') {
  72. const slicedHex = buffer.slice(rule.start || 0, rule.end || buffer.length).toString('hex');
  73. isDetected = (slicedHex === rule.bytes);
  74. return this.isReturnFalse(isDetected, type);
  75. }
  76. if (rule.type === 'notEqual') {
  77. const slicedHex = buffer.slice(rule.start || 0, rule.end || buffer.length).toString('hex');
  78. isDetected = !(slicedHex === rule.bytes);
  79. return this.isReturnFalse(isDetected, type);
  80. }
  81. if (rule.type === 'contains') {
  82. const slicedHex = buffer.slice(rule.start || 0, rule.end || buffer.length).toString('hex');
  83. if (typeof rule.bytes === 'string') {
  84. rule.bytes = [rule.bytes];
  85. }
  86. rule.bytes.every((bytes) => {
  87. isDetected = (slicedHex.indexOf(bytes) !== -1);
  88. return isDetected;
  89. });
  90. return this.isReturnFalse(isDetected, type);
  91. }
  92. if (rule.type === 'notContains') {
  93. const slicedHex = buffer.slice(rule.start || 0, rule.end || buffer.length).toString('hex');
  94. if (typeof rule.bytes === 'string') {
  95. rule.bytes = [rule.bytes];
  96. }
  97. rule.bytes.every((bytes) => {
  98. isDetected = (slicedHex.indexOf(bytes) === -1);
  99. return isDetected;
  100. });
  101. return this.isReturnFalse(isDetected, type);
  102. }
  103. if (rule.type === 'or') {
  104. isDetected = this.detect(buffer, rule.rules, 'or');
  105. return this.isReturnFalse(isDetected, type);
  106. }
  107. if (rule.type === 'and') {
  108. isDetected = this.detect(buffer, rule.rules, 'and');
  109. return this.isReturnFalse(isDetected, type);
  110. }
  111. return true;
  112. });
  113. return isDetected;
  114. }
  115. isReturnFalse(isDetected, type) {
  116. if (!isDetected && type === 'and') {
  117. return false;
  118. }
  119. if (isDetected && type === 'or') {
  120. return false;
  121. }
  122. return true;
  123. }
  124. validateRuleType(rule) {
  125. const types = ['or', 'and', 'contains', 'notContains', 'equal', 'notEqual'];
  126. return (types.indexOf(rule.type) !== -1);
  127. }
  128. validateSigantures() {
  129. let invalidSignatures = signatures.map((signature) => {
  130. return this.validateSignature(signature);
  131. });
  132. invalidSignatures = this.cleanArray(invalidSignatures);
  133. if (invalidSignatures.length) {
  134. return invalidSignatures;
  135. }
  136. return true;
  137. }
  138. validateSignature(signature) {
  139. if (!('type' in signature)) {
  140. return {
  141. message: 'signature does not contain "type" field',
  142. signature
  143. };
  144. }
  145. if (!('ext' in signature)) {
  146. return {
  147. message: 'signature does not contain "ext" field',
  148. signature
  149. };
  150. }
  151. if (!('mime' in signature)) {
  152. return {
  153. message: 'signature does not contain "mime" field',
  154. signature
  155. };
  156. }
  157. if (!('rules' in signature)) {
  158. return {
  159. message: 'signature does not contain "rules" field',
  160. signature
  161. };
  162. }
  163. const invalidRules = this.validateRules(signature.rules);
  164. if (invalidRules && invalidRules.length) {
  165. return {
  166. message: 'signature has invalid rule',
  167. signature,
  168. rules: invalidRules
  169. }
  170. }
  171. }
  172. validateRules(rules) {
  173. let invalidRules = rules.map((rule) => {
  174. let isRuleTypeValid = this.validateRuleType(rule);
  175. if (!isRuleTypeValid) {
  176. return {
  177. message: 'rule type does not supported',
  178. rule
  179. };
  180. }
  181. if ((rule.type === 'or' || rule.type === 'and') && !('rules' in rule)) {
  182. return {
  183. message: 'rule should contains "rules" field',
  184. rule
  185. };
  186. }
  187. if (rule.type === 'or' || rule.type === 'and') {
  188. return this.validateRules(rule.rules);
  189. }
  190. return false;
  191. });
  192. invalidRules = this.cleanArray(invalidRules);
  193. if (invalidRules.length) {
  194. return invalidRules;
  195. }
  196. }
  197. cleanArray(actual) {
  198. let newArray = new Array();
  199. for (let i = 0; i < actual.length; i++) {
  200. if (actual[i]) {
  201. newArray.push(actual[i]);
  202. }
  203. }
  204. return newArray;
  205. }
  206. addSignature(signature) {
  207. signatures.push(signature);
  208. }
  209. getFileSize(filePath, callback) {
  210. fs.stat(filePath, (err, stat) => {
  211. if (err) {
  212. return callback(err);
  213. }
  214. return callback(null, stat.size);
  215. });
  216. }
  217. }
  218. module.exports = FileDetector;