Search code examples
windowsfileperlzip

Getting "format error: can't find EOCD signature" with archives


The code below works fine, except that for a few of the zip files I am getting the error

format error: can't find EOCD signature 
 at C:/LegacyApp/perl/lib/Archive/Zip/Archive.pm line 695
    Archive::Zip::Archive::_findEndOfCentralDirectory('Archive::Zip::Archive=HASH(0x375a730)', 'IO::File=GLOB(0x380eb90)') called at C:/LegacyApp/perl/lib/Archive/Zip/Archive.pm line 581
    Archive::Zip::Archive::readFromFileHandle('Archive::Zip::Archive=HASH(0x375a730)', 'IO::File=GLOB(0x380eb90)',

Using below piece of code:

use Archive::Zip;

unzip($zipfile,$folder,$out);

sub unzip {
    my ($archive, $want, $dir) = @_;
    my $zip = Archive::Zip->new($archive);
    foreach my $file ($zip->members) {      
        if (($file->fileName =~ /VERSION\/(.*?).cosipa.xlsx$/i)) {
            $zip->extractMember($file,$dir.$file->fileName);
        }
        if ($file->fileName =~ /VERSION\/(.*?).txt$/i) {
            $zip->extractMember($file,$dir.$file->fileName);
        }
    }
    return 1; 
}

If i delete that particular zip file, then it works fine. But I need a solution where i should be able to handle these zip files also and if possible please let me know what is wrong in code and zip files.

Thanks in advance


Solution

  • It seems that some of those files indeed have format errors. Then you can catch those failures and handle the bad files (record names or delete files, etc), and otherwise proceed normally.

    It isn't clear whether you are getting warnings (the program complains and continues) or errors (an issued die is killing the program) and they need be handled differently.

    If the shown "error" is an exception (the program dies) then you can trap and handle that

    eval { unzip($zipfile, $folder, $out) };
    if ($@) {
        say "Error: $@";
        # Interrogate. (Is it the expected error or some other?)
        # handle it: record the filename for later/delete it/etc ...
    }
    # control returns here, unless the block above does exit/die/croak/goto
    

    A builtin way to handle exceptions in Perl (the die) is via the block form of eval. Also see the $@ error variable in perlvar.

    If your message is a mere warning and the program continues then the eval can't catch that, as it only traps exceptions, not warnings. One way to detect that is to install a SIG{__WARN__} hook and in it throw a die, and then the same eval as above will work

    # Block, so the change to how warnings work is scoped as tight as possible
    HANDLE_ZIP: {
        local $SIG{__WARN__} = sub {
            die  $_[0] if $_[0] =~ /^\s*format error:/;  # raise exception, or
            warn $_[0];                                  # re-emit the warning
        };
    
        eval { unzip($zipfile, $folder, $out) };
        if ($@) {
            # same as above...
        }
    };
    

    Now if a warning comes out of unzip, instead of it being printed the sub with reference assigned to $SIG{__WARN__} is invoked. Then, if the warning's message is matched by that regex, a die is thrown instead, with that message. Since this is triggered inside the eval it is handled, as in the previous code sample. See %SIG in perlvar.

    Another way is to simply raise an exception in $SIG{__WARN__} for any warning (only within this block!) and then deal with all details in the eval's handler.

    Note that local is important in all this, so that we don't change how warnings work across all code but only in this block.

    This should work as it stands but please study the linked docs.


    There used to be subtle traps with handling $@ directly (prior to v5.14). While that had been resolved, all this is indeed low-level and it may be a good idea to consider using a module instead. That would wrap eval+$@ for easier digestion, and perhaps for easier correct usage.