structures.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. 'use strict';
  2. var binary = require('binary');
  3. var convertDateTime = function (dosDate, dosTime) {
  4. var year = ((dosDate >> 9) & 0x7F) + 1980;
  5. var month = (dosDate >> 5) & 0x0F;
  6. var day = dosDate & 0x1F;
  7. var hour = (dosTime >> 11);
  8. var minute = (dosTime >> 5) & 0x3F;
  9. var second = (dosTime & 0x1F) * 2;
  10. var result = new Date(year, month - 1, day, hour, minute, second, 0);
  11. return result;
  12. };
  13. var convertGeneralPurposeFlags = function (value) {
  14. var bits = [];
  15. for (var i = 0; i < 16; i++) {
  16. bits[i] = (value >> i) & 1;
  17. }
  18. return {
  19. encrypted: !!bits[0],
  20. compressionFlag1: !!bits[1],
  21. compressionFlag2: !!bits[2],
  22. useDataDescriptor: !!bits[3],
  23. enhancedDeflating: !!bits[4],
  24. compressedPatched: !!bits[5],
  25. strongEncryption: !!bits[6],
  26. utf8: !!bits[11],
  27. encryptedCD: !!bits[13]
  28. };
  29. };
  30. var parseExternalFileAttributes = function (externalAttributes, platform) {
  31. var types = {
  32. // In theory, any of these could be set. Realistically, though, it will
  33. // be regular, directory or symlink
  34. 1: 'NamedPipe',
  35. 2: 'Character',
  36. 4: 'Directory',
  37. 6: 'Block',
  38. 8: 'File',
  39. 10: 'SymbolicLink',
  40. 12: 'Socket'
  41. };
  42. switch (platform) {
  43. case 3: // Unix
  44. return {
  45. platform: 'Unix',
  46. type: types[(externalAttributes >> 28) & 0x0F],
  47. mode: (externalAttributes >> 16) & 0xFFF
  48. };
  49. // case 0: // MSDOS
  50. default:
  51. if (platform !== 0) {
  52. console.warn('Possibly unsupported ZIP platform type, ' + platform);
  53. }
  54. var attribs = {
  55. A: (externalAttributes >> 5) & 0x01,
  56. D: (externalAttributes >> 4) & 0x01,
  57. V: (externalAttributes >> 3) & 0x01,
  58. S: (externalAttributes >> 2) & 0x01,
  59. H: (externalAttributes >> 1) & 0x01,
  60. R: externalAttributes & 0x01
  61. };
  62. // With no better guidance we'll make the default permissions ugo+r
  63. var mode = parseInt('0444', 8);
  64. if (attribs.D) {
  65. mode |= parseInt('0111', 8); // Set the execute bit
  66. }
  67. if (!attribs.R) {
  68. mode |= parseInt('0222', 8); // Set the write bit
  69. }
  70. mode &= ~process.umask();
  71. return {
  72. platform: 'DOS',
  73. type: attribs.D ? 'Directory' : 'File',
  74. mode: mode
  75. };
  76. }
  77. };
  78. var readEndRecord = function (buffer) {
  79. var data = binary.parse(buffer)
  80. .word32lu('signature')
  81. .word16lu('diskNumber')
  82. .word16lu('directoryStartDisk')
  83. .word16lu('directoryEntryCountDisk')
  84. .word16lu('directoryEntryCount')
  85. .word32lu('directorySize')
  86. .word32lu('directoryOffset')
  87. .word16lu('commentLength')
  88. .buffer('comment', 'commentLength')
  89. .vars;
  90. data.comment = data.comment.toString();
  91. return data;
  92. };
  93. var directorySort = function (a, b) {
  94. return a.relativeOffsetOfLocalHeader - b.relativeOffsetOfLocalHeader;
  95. };
  96. var readDirectory = function (buffer) {
  97. var directory = [];
  98. var current;
  99. var index = 0;
  100. while (index < buffer.length) {
  101. current = binary.parse(buffer.slice(index, index + 46))
  102. .word32lu('signature')
  103. .word8lu('creatorSpecVersion')
  104. .word8lu('creatorPlatform')
  105. .word8lu('requiredSpecVersion')
  106. .word8lu('requiredPlatform')
  107. .word16lu('generalPurposeBitFlag')
  108. .word16lu('compressionMethod')
  109. .word16lu('lastModFileTime')
  110. .word16lu('lastModFileDate')
  111. .word32lu('crc32')
  112. .word32lu('compressedSize')
  113. .word32lu('uncompressedSize')
  114. .word16lu('fileNameLength')
  115. .word16lu('extraFieldLength')
  116. .word16lu('fileCommentLength')
  117. .word16lu('diskNumberStart')
  118. .word16lu('internalFileAttributes')
  119. .word32lu('externalFileAttributes')
  120. .word32lu('relativeOffsetOfLocalHeader')
  121. .vars;
  122. index += 46;
  123. current.generalPurposeFlags = convertGeneralPurposeFlags(current.generalPurposeBitFlag);
  124. current.fileAttributes = parseExternalFileAttributes(current.externalFileAttributes, current.creatorPlatform);
  125. current.modifiedTime = convertDateTime(current.lastModFileDate, current.lastModFileTime);
  126. current.fileName = current.extraField = current.fileComment = '';
  127. current.headerLength = 46 + current.fileNameLength + current.extraFieldLength + current.fileCommentLength;
  128. if (current.fileNameLength > 0) {
  129. current.fileName = buffer.slice(index, index + current.fileNameLength).toString();
  130. index += current.fileNameLength;
  131. }
  132. if (current.extraFieldLength > 0) {
  133. current.extraField = buffer.slice(index, index + current.extraFieldLength).toString();
  134. index += current.extraFieldLength;
  135. }
  136. if (current.fileCommentLength > 0) {
  137. current.fileComment = buffer.slice(index, index + current.fileCommentLength).toString();
  138. index += current.fileCommentLength;
  139. }
  140. if (current.fileAttributes.type !== 'Directory' && current.fileName.substr(-1) === '/') {
  141. // TODO: check that this is a reasonable check
  142. current.fileAttributes.type = 'Directory';
  143. }
  144. directory.push(current);
  145. }
  146. directory.sort(directorySort);
  147. return directory;
  148. };
  149. var readFileEntry = function (buffer) {
  150. var index = 0;
  151. var fileEntry = binary.parse(buffer.slice(index, 30))
  152. .word32lu('signature')
  153. .word16lu('versionNeededToExtract')
  154. .word16lu('generalPurposeBitFlag')
  155. .word16lu('compressionMethod')
  156. .word16lu('lastModFileTime')
  157. .word16lu('lastModFileDate')
  158. .word32lu('crc32')
  159. .word32lu('compressedSize')
  160. .word32lu('uncompressedSize')
  161. .word16lu('fileNameLength')
  162. .word16lu('extraFieldLength')
  163. .vars;
  164. index += 30;
  165. fileEntry.fileName = fileEntry.extraField = '';
  166. fileEntry.entryLength = 30 + fileEntry.fileNameLength + fileEntry.extraFieldLength;
  167. if (fileEntry.entryLength > structures.maxFileEntrySize) {
  168. throw new Error('File entry unexpectedly large: ' + fileEntry.entryLength + ' (max: ' + structures.maxFileEntrySize + ')');
  169. }
  170. if (fileEntry.fileNameLength > 0) {
  171. fileEntry.fileName = buffer.slice(index, index + fileEntry.fileNameLength).toString();
  172. index += fileEntry.fileNameLength;
  173. }
  174. if (fileEntry.extraFieldLength > 0) {
  175. fileEntry.extraField = buffer.slice(index, index + fileEntry.extraFieldLength).toString();
  176. index += fileEntry.extraFieldLength;
  177. }
  178. return fileEntry;
  179. };
  180. var structures = module.exports = {
  181. readEndRecord: readEndRecord,
  182. readDirectory: readDirectory,
  183. readFileEntry: readFileEntry,
  184. maxFileEntrySize: 4096
  185. };