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