暫無描述

zipEntry.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. 'use strict';
  2. var readerFor = require('./reader/readerFor');
  3. var utils = require('./utils');
  4. var CompressedObject = require('./compressedObject');
  5. var crc32fn = require('./crc32');
  6. var utf8 = require('./utf8');
  7. var compressions = require('./compressions');
  8. var support = require('./support');
  9. var MADE_BY_DOS = 0x00;
  10. var MADE_BY_UNIX = 0x03;
  11. /**
  12. * Find a compression registered in JSZip.
  13. * @param {string} compressionMethod the method magic to find.
  14. * @return {Object|null} the JSZip compression object, null if none found.
  15. */
  16. var findCompression = function(compressionMethod) {
  17. for (var method in compressions) {
  18. if (!compressions.hasOwnProperty(method)) {
  19. continue;
  20. }
  21. if (compressions[method].magic === compressionMethod) {
  22. return compressions[method];
  23. }
  24. }
  25. return null;
  26. };
  27. // class ZipEntry {{{
  28. /**
  29. * An entry in the zip file.
  30. * @constructor
  31. * @param {Object} options Options of the current file.
  32. * @param {Object} loadOptions Options for loading the stream.
  33. */
  34. function ZipEntry(options, loadOptions) {
  35. this.options = options;
  36. this.loadOptions = loadOptions;
  37. }
  38. ZipEntry.prototype = {
  39. /**
  40. * say if the file is encrypted.
  41. * @return {boolean} true if the file is encrypted, false otherwise.
  42. */
  43. isEncrypted: function() {
  44. // bit 1 is set
  45. return (this.bitFlag & 0x0001) === 0x0001;
  46. },
  47. /**
  48. * say if the file has utf-8 filename/comment.
  49. * @return {boolean} true if the filename/comment is in utf-8, false otherwise.
  50. */
  51. useUTF8: function() {
  52. // bit 11 is set
  53. return (this.bitFlag & 0x0800) === 0x0800;
  54. },
  55. /**
  56. * Read the local part of a zip file and add the info in this object.
  57. * @param {DataReader} reader the reader to use.
  58. */
  59. readLocalPart: function(reader) {
  60. var compression, localExtraFieldsLength;
  61. // we already know everything from the central dir !
  62. // If the central dir data are false, we are doomed.
  63. // On the bright side, the local part is scary : zip64, data descriptors, both, etc.
  64. // The less data we get here, the more reliable this should be.
  65. // Let's skip the whole header and dash to the data !
  66. reader.skip(22);
  67. // in some zip created on windows, the filename stored in the central dir contains \ instead of /.
  68. // Strangely, the filename here is OK.
  69. // I would love to treat these zip files as corrupted (see http://www.info-zip.org/FAQ.html#backslashes
  70. // or APPNOTE#4.4.17.1, "All slashes MUST be forward slashes '/'") but there are a lot of bad zip generators...
  71. // Search "unzip mismatching "local" filename continuing with "central" filename version" on
  72. // the internet.
  73. //
  74. // I think I see the logic here : the central directory is used to display
  75. // content and the local directory is used to extract the files. Mixing / and \
  76. // may be used to display \ to windows users and use / when extracting the files.
  77. // Unfortunately, this lead also to some issues : http://seclists.org/fulldisclosure/2009/Sep/394
  78. this.fileNameLength = reader.readInt(2);
  79. localExtraFieldsLength = reader.readInt(2); // can't be sure this will be the same as the central dir
  80. // the fileName is stored as binary data, the handleUTF8 method will take care of the encoding.
  81. this.fileName = reader.readData(this.fileNameLength);
  82. reader.skip(localExtraFieldsLength);
  83. if (this.compressedSize === -1 || this.uncompressedSize === -1) {
  84. throw new Error("Bug or corrupted zip : didn't get enough informations from the central directory " + "(compressedSize === -1 || uncompressedSize === -1)");
  85. }
  86. compression = findCompression(this.compressionMethod);
  87. if (compression === null) { // no compression found
  88. throw new Error("Corrupted zip : compression " + utils.pretty(this.compressionMethod) + " unknown (inner file : " + utils.transformTo("string", this.fileName) + ")");
  89. }
  90. this.decompressed = new CompressedObject(this.compressedSize, this.uncompressedSize, this.crc32, compression, reader.readData(this.compressedSize));
  91. },
  92. /**
  93. * Read the central part of a zip file and add the info in this object.
  94. * @param {DataReader} reader the reader to use.
  95. */
  96. readCentralPart: function(reader) {
  97. this.versionMadeBy = reader.readInt(2);
  98. reader.skip(2);
  99. // this.versionNeeded = reader.readInt(2);
  100. this.bitFlag = reader.readInt(2);
  101. this.compressionMethod = reader.readString(2);
  102. this.date = reader.readDate();
  103. this.crc32 = reader.readInt(4);
  104. this.compressedSize = reader.readInt(4);
  105. this.uncompressedSize = reader.readInt(4);
  106. var fileNameLength = reader.readInt(2);
  107. this.extraFieldsLength = reader.readInt(2);
  108. this.fileCommentLength = reader.readInt(2);
  109. this.diskNumberStart = reader.readInt(2);
  110. this.internalFileAttributes = reader.readInt(2);
  111. this.externalFileAttributes = reader.readInt(4);
  112. this.localHeaderOffset = reader.readInt(4);
  113. if (this.isEncrypted()) {
  114. throw new Error("Encrypted zip are not supported");
  115. }
  116. // will be read in the local part, see the comments there
  117. reader.skip(fileNameLength);
  118. this.readExtraFields(reader);
  119. this.parseZIP64ExtraField(reader);
  120. this.fileComment = reader.readData(this.fileCommentLength);
  121. },
  122. /**
  123. * Parse the external file attributes and get the unix/dos permissions.
  124. */
  125. processAttributes: function () {
  126. this.unixPermissions = null;
  127. this.dosPermissions = null;
  128. var madeBy = this.versionMadeBy >> 8;
  129. // Check if we have the DOS directory flag set.
  130. // We look for it in the DOS and UNIX permissions
  131. // but some unknown platform could set it as a compatibility flag.
  132. this.dir = this.externalFileAttributes & 0x0010 ? true : false;
  133. if(madeBy === MADE_BY_DOS) {
  134. // first 6 bits (0 to 5)
  135. this.dosPermissions = this.externalFileAttributes & 0x3F;
  136. }
  137. if(madeBy === MADE_BY_UNIX) {
  138. this.unixPermissions = (this.externalFileAttributes >> 16) & 0xFFFF;
  139. // the octal permissions are in (this.unixPermissions & 0x01FF).toString(8);
  140. }
  141. // fail safe : if the name ends with a / it probably means a folder
  142. if (!this.dir && this.fileNameStr.slice(-1) === '/') {
  143. this.dir = true;
  144. }
  145. },
  146. /**
  147. * Parse the ZIP64 extra field and merge the info in the current ZipEntry.
  148. * @param {DataReader} reader the reader to use.
  149. */
  150. parseZIP64ExtraField: function(reader) {
  151. if (!this.extraFields[0x0001]) {
  152. return;
  153. }
  154. // should be something, preparing the extra reader
  155. var extraReader = readerFor(this.extraFields[0x0001].value);
  156. // I really hope that these 64bits integer can fit in 32 bits integer, because js
  157. // won't let us have more.
  158. if (this.uncompressedSize === utils.MAX_VALUE_32BITS) {
  159. this.uncompressedSize = extraReader.readInt(8);
  160. }
  161. if (this.compressedSize === utils.MAX_VALUE_32BITS) {
  162. this.compressedSize = extraReader.readInt(8);
  163. }
  164. if (this.localHeaderOffset === utils.MAX_VALUE_32BITS) {
  165. this.localHeaderOffset = extraReader.readInt(8);
  166. }
  167. if (this.diskNumberStart === utils.MAX_VALUE_32BITS) {
  168. this.diskNumberStart = extraReader.readInt(4);
  169. }
  170. },
  171. /**
  172. * Read the central part of a zip file and add the info in this object.
  173. * @param {DataReader} reader the reader to use.
  174. */
  175. readExtraFields: function(reader) {
  176. var end = reader.index + this.extraFieldsLength,
  177. extraFieldId,
  178. extraFieldLength,
  179. extraFieldValue;
  180. if (!this.extraFields) {
  181. this.extraFields = {};
  182. }
  183. while (reader.index < end) {
  184. extraFieldId = reader.readInt(2);
  185. extraFieldLength = reader.readInt(2);
  186. extraFieldValue = reader.readData(extraFieldLength);
  187. this.extraFields[extraFieldId] = {
  188. id: extraFieldId,
  189. length: extraFieldLength,
  190. value: extraFieldValue
  191. };
  192. }
  193. },
  194. /**
  195. * Apply an UTF8 transformation if needed.
  196. */
  197. handleUTF8: function() {
  198. var decodeParamType = support.uint8array ? "uint8array" : "array";
  199. if (this.useUTF8()) {
  200. this.fileNameStr = utf8.utf8decode(this.fileName);
  201. this.fileCommentStr = utf8.utf8decode(this.fileComment);
  202. } else {
  203. var upath = this.findExtraFieldUnicodePath();
  204. if (upath !== null) {
  205. this.fileNameStr = upath;
  206. } else {
  207. // ASCII text or unsupported code page
  208. var fileNameByteArray = utils.transformTo(decodeParamType, this.fileName);
  209. this.fileNameStr = this.loadOptions.decodeFileName(fileNameByteArray);
  210. }
  211. var ucomment = this.findExtraFieldUnicodeComment();
  212. if (ucomment !== null) {
  213. this.fileCommentStr = ucomment;
  214. } else {
  215. // ASCII text or unsupported code page
  216. var commentByteArray = utils.transformTo(decodeParamType, this.fileComment);
  217. this.fileCommentStr = this.loadOptions.decodeFileName(commentByteArray);
  218. }
  219. }
  220. },
  221. /**
  222. * Find the unicode path declared in the extra field, if any.
  223. * @return {String} the unicode path, null otherwise.
  224. */
  225. findExtraFieldUnicodePath: function() {
  226. var upathField = this.extraFields[0x7075];
  227. if (upathField) {
  228. var extraReader = readerFor(upathField.value);
  229. // wrong version
  230. if (extraReader.readInt(1) !== 1) {
  231. return null;
  232. }
  233. // the crc of the filename changed, this field is out of date.
  234. if (crc32fn(this.fileName) !== extraReader.readInt(4)) {
  235. return null;
  236. }
  237. return utf8.utf8decode(extraReader.readData(upathField.length - 5));
  238. }
  239. return null;
  240. },
  241. /**
  242. * Find the unicode comment declared in the extra field, if any.
  243. * @return {String} the unicode comment, null otherwise.
  244. */
  245. findExtraFieldUnicodeComment: function() {
  246. var ucommentField = this.extraFields[0x6375];
  247. if (ucommentField) {
  248. var extraReader = readerFor(ucommentField.value);
  249. // wrong version
  250. if (extraReader.readInt(1) !== 1) {
  251. return null;
  252. }
  253. // the crc of the comment changed, this field is out of date.
  254. if (crc32fn(this.fileComment) !== extraReader.readInt(4)) {
  255. return null;
  256. }
  257. return utf8.utf8decode(extraReader.readData(ucommentField.length - 5));
  258. }
  259. return null;
  260. }
  261. };
  262. module.exports = ZipEntry;