@@ -245,15 +245,85 @@ ZipEntries.prototype = {
245245 prepareReader : function ( data ) {
246246 this . reader = readerFor ( data ) ;
247247 } ,
248+ /**
249+ * Attempt to parse a zip without a central directory.
250+ */
251+ tryRecoverCorruptedZip : function ( ) {
252+ // Undo anything done by the central directory reader
253+ this . reader . setIndex ( 0 ) ;
254+ this . files = [ ] ;
255+
256+ // Zip comment is in central directory, so we have no comment
257+ this . zipCommentLength = 0 ;
258+ this . zipComment = [ ] ;
259+
260+ let possibleHeaders = 0 ;
261+
262+ // Without central directory, have to just search for file entry magic bytes
263+ const length = this . reader . length ;
264+ for ( let i = 0 ; i < length - 4 ; i ++ ) {
265+ if (
266+ this . reader . byteAt ( i ) === 0x50 &&
267+ this . reader . byteAt ( i + 1 ) === 0x4B &&
268+ this . reader . byteAt ( i + 2 ) === 0x03 &&
269+ this . reader . byteAt ( i + 3 ) === 0x04
270+ ) {
271+ possibleHeaders ++ ;
272+ const zipEntry = this . tryRecoverFileEntry ( i ) ;
273+ if ( zipEntry ) {
274+ this . files . push ( zipEntry ) ;
275+ }
276+ }
277+ }
278+
279+ if ( this . files . length === 0 ) {
280+ if ( possibleHeaders === 0 ) {
281+ throw new Error ( "Corrupted zip: no central directory or any file headers" ) ;
282+ }
283+ throw new Error ( `Corrupted zip: no central directory, and ${ possibleHeaders } possible local headers could not be recovered` ) ;
284+ }
285+ } ,
286+ /**
287+ * Attempts to read a zip entry from only the local header.
288+ * @param {number } index Index in this.reader where a file header appears to start
289+ * @returns {ZipEntry|null } The zip entry if a valid one could be parsed, otherwise null.
290+ */
291+ tryRecoverFileEntry : function ( index ) {
292+ this . reader . setIndex ( index ) ;
293+
294+ const zipEntry = new ZipEntry ( {
295+ zip64 : this . zip64
296+ } , this . loadOptions ) ;
297+
298+ try {
299+ zipEntry . readLocalPartFromCorruptedZip ( this . reader ) ;
300+ zipEntry . handleUTF8 ( ) ;
301+ zipEntry . processAttributes ( ) ;
302+ return zipEntry ;
303+ } catch ( e ) {
304+ // Entry is invalid.
305+ console . error ( e ) ;
306+ return null ;
307+ }
308+ } ,
248309 /**
249310 * Read a zip file and create ZipEntries.
250311 * @param {String|ArrayBuffer|Uint8Array|Buffer } data the binary string representing a zip file.
251312 */
252313 load : function ( data ) {
253314 this . prepareReader ( data ) ;
254- this . readEndOfCentral ( ) ;
255- this . readCentralDir ( ) ;
256- this . readLocalFiles ( ) ;
315+
316+ try {
317+ this . readEndOfCentral ( ) ;
318+ this . readCentralDir ( ) ;
319+ this . readLocalFiles ( ) ;
320+ } catch ( error ) {
321+ if ( this . loadOptions . recoverCorrupted ) {
322+ this . tryRecoverCorruptedZip ( ) ;
323+ } else {
324+ throw error ;
325+ }
326+ }
257327 }
258328} ;
259329// }}} end of ZipEntries
0 commit comments