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, 10000, (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. //console.log(buffer);
  47. const invalidSignaturesList = this.validateSigantures();
  48. if (invalidSignaturesList.length) {
  49. return callback(invalidSignaturesList);
  50. }
  51. signatures.every((signature) => {
  52. if (this.detect(buffer, signature.rules)) {
  53. result = {
  54. ext: signature.ext,
  55. mime: signature.mime
  56. };
  57. if (signature.iana)
  58. result.iana = signature.iana;
  59. return false;
  60. }
  61. return true;
  62. });
  63. callback(null, result);
  64. }
  65. detect(buffer, receivedRules, type) {
  66. if (!type) {
  67. type = 'and';
  68. }
  69. const rules = [...receivedRules];
  70. let isDetected = true;
  71. rules.every((rule) => {
  72. if (rule.type === 'equal') {
  73. const slicedHex = buffer.slice(rule.start || 0, rule.end || buffer.length).toString('hex');
  74. isDetected = (slicedHex === rule.bytes);
  75. return this.isReturnFalse(isDetected, type);
  76. }
  77. if (rule.type === 'notEqual') {
  78. const slicedHex = buffer.slice(rule.start || 0, rule.end || buffer.length).toString('hex');
  79. isDetected = !(slicedHex === rule.bytes);
  80. return this.isReturnFalse(isDetected, type);
  81. }
  82. if (rule.type === 'contains') {
  83. const slicedHex = buffer.slice(rule.start || 0, rule.end || buffer.length).toString('hex');
  84. if (typeof rule.bytes === 'string') {
  85. rule.bytes = [rule.bytes];
  86. }
  87. rule.bytes.every((bytes) => {
  88. isDetected = (slicedHex.indexOf(bytes) !== -1);
  89. return isDetected;
  90. });
  91. return this.isReturnFalse(isDetected, type);
  92. }
  93. if (rule.type === 'notContains') {
  94. const slicedHex = buffer.slice(rule.start || 0, rule.end || buffer.length).toString('hex');
  95. if (typeof rule.bytes === 'string') {
  96. rule.bytes = [rule.bytes];
  97. }
  98. rule.bytes.every((bytes) => {
  99. isDetected = (slicedHex.indexOf(bytes) === -1);
  100. return isDetected;
  101. });
  102. return this.isReturnFalse(isDetected, type);
  103. }
  104. if (rule.type === 'or') {
  105. isDetected = this.detect(buffer, rule.rules, 'or');
  106. return this.isReturnFalse(isDetected, type);
  107. }
  108. if (rule.type === 'and') {
  109. isDetected = this.detect(buffer, rule.rules, 'and');
  110. return this.isReturnFalse(isDetected, type);
  111. }
  112. return true;
  113. });
  114. return isDetected;
  115. }
  116. isReturnFalse(isDetected, type) {
  117. if (!isDetected && type === 'and') {
  118. return false;
  119. }
  120. if (isDetected && type === 'or') {
  121. return false;
  122. }
  123. return true;
  124. }
  125. validateRuleType(rule) {
  126. const types = ['or', 'and', 'contains', 'notContains', 'equal', 'notEqual'];
  127. return (types.indexOf(rule.type) !== -1);
  128. }
  129. validateSigantures() {
  130. let invalidSignatures = signatures.map((signature) => {
  131. return this.validateSignature(signature);
  132. });
  133. invalidSignatures = this.cleanArray(invalidSignatures);
  134. if (invalidSignatures.length) {
  135. return invalidSignatures;
  136. }
  137. return true;
  138. }
  139. validateSignature(signature) {
  140. if (!('type' in signature)) {
  141. return {
  142. message: 'signature does not contain "type" field',
  143. signature
  144. };
  145. }
  146. if (!('ext' in signature)) {
  147. return {
  148. message: 'signature does not contain "ext" field',
  149. signature
  150. };
  151. }
  152. if (!('mime' in signature)) {
  153. return {
  154. message: 'signature does not contain "mime" field',
  155. signature
  156. };
  157. }
  158. if (!('rules' in signature)) {
  159. return {
  160. message: 'signature does not contain "rules" field',
  161. signature
  162. };
  163. }
  164. const invalidRules = this.validateRules(signature.rules);
  165. if (invalidRules && invalidRules.length) {
  166. return {
  167. message: 'signature has invalid rule',
  168. signature,
  169. rules: invalidRules
  170. }
  171. }
  172. }
  173. validateRules(rules) {
  174. let invalidRules = rules.map((rule) => {
  175. let isRuleTypeValid = this.validateRuleType(rule);
  176. if (!isRuleTypeValid) {
  177. return {
  178. message: 'rule type does not supported',
  179. rule
  180. };
  181. }
  182. if ((rule.type === 'or' || rule.type === 'and') && !('rules' in rule)) {
  183. return {
  184. message: 'rule should contains "rules" field',
  185. rule
  186. };
  187. }
  188. if (rule.type === 'or' || rule.type === 'and') {
  189. return this.validateRules(rule.rules);
  190. }
  191. return false;
  192. });
  193. invalidRules = this.cleanArray(invalidRules);
  194. if (invalidRules.length) {
  195. return invalidRules;
  196. }
  197. }
  198. cleanArray(actual) {
  199. let newArray = new Array();
  200. for (let i = 0; i < actual.length; i++) {
  201. if (actual[i]) {
  202. newArray.push(actual[i]);
  203. }
  204. }
  205. return newArray;
  206. }
  207. addSignature(signature) {
  208. signatures.push(signature);
  209. }
  210. getFileSize(filePath, callback) {
  211. fs.stat(filePath, (err, stat) => {
  212. if (err) {
  213. return callback(err);
  214. }
  215. return callback(null, stat.size);
  216. });
  217. }
  218. }
  219. module.exports = FileDetector;