Browse Source

Add a script to fix onedrive zips

Samuel W. Flint 6 months ago
parent
commit
7ec0acb617
1 changed files with 164 additions and 0 deletions
  1. 164 0
      fix-onedrive-zip

+ 164 - 0
fix-onedrive-zip

@@ -0,0 +1,164 @@
+#!/usr/bin/env perl
+
+# fix-onedrive-zip
+#
+# Fix OneDrive/Windows Zip files larger than 4Gig that have an invalid
+# 'Total Number of Disks' field in the 'ZIP64 End Central Directory
+# Locator'. The value in this field should be 1, but OneDrive/Windows sets
+# it to 0.  This makes it difficult to work with these files using standard
+# unzip utilities.
+#
+# This program changes the 'Total Number of Disks' field value to 1.
+#
+# Copyright (c) 2020-2023 Paul Marquess. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the same terms as Perl itself.
+
+use strict;
+use warnings;
+
+use Fcntl qw(SEEK_SET SEEK_END);
+use Getopt::Long;
+
+# Signatures for the headers we need to check
+use constant ZIP_LOCAL_HDR_SIG                 => 0x04034b50;
+use constant ZIP_END_CENTRAL_HDR_SIG           => 0x06054b50;
+use constant ZIP64_END_CENTRAL_LOC_HDR_SIG     => 0x07064b50;
+
+sub Seek;
+sub Read;
+
+my $VERSION = '1.02' ;
+my $dry_run ;
+
+BEGIN {
+    # Check for a 32-bit Perl
+    if (!eval { pack "Q", 1 }) {
+        warn "The perl executable you are running is 32-bit.\n" .
+             "You need to install a 64-bit perl executable to continue\n";
+        exit(1);
+    }
+}
+
+GetOptions ("dry-run" => \$dry_run,
+            "help"    => \&Usage)
+    or Usage("Error in command line arguments\n");
+
+Usage()
+    unless @ARGV >= 1;
+
+for my $filename (@ARGV)
+{
+    open my $fh,  "+<$filename"
+        or die "Error: Cannot open '$filename': $!\n";
+}
+
+for my $filename (@ARGV)
+{
+    print "\nChecking '$filename'\n";
+    open my $fh,  "+<$filename"
+        or die "Cannot open '$filename': $!\n";
+
+    my $data = Read $filename, $fh, 4;
+
+    my $sig = unpack("V", $data) ;
+
+    die "Error: No Zip signature at start of '$filename'\n"
+        unless $sig == ZIP_LOCAL_HDR_SIG ;
+
+    # Assume no comment or other trailing data
+    # The last two things in the file are the Z64 & EOCD records
+
+    Seek  $filename, $fh, -42 ;
+
+    $data = Read $filename, $fh, 42;
+
+    my $eocd = substr($data, 20);
+    my $eocd_sig = unpack("V", substr($eocd, 0, 4)) ;
+
+    die "Error: Cannot find Zip signature at end of '$filename'\n"
+        unless $eocd_sig == ZIP_END_CENTRAL_HDR_SIG ;
+
+    my $offset = unpack("VV", substr($eocd, 16, 4)) ;
+
+    die sprintf "Error: bad offset 0x%X at end of '$filename'\n", $offset
+        unless $offset == 0xFFFFFFFF ;
+
+    my $z64_sig = unpack("V", substr($data, 0, 4)) ;
+
+    die "Error: Cannot find Zip64 signature at end of '$filename'\n"
+        unless $z64_sig == ZIP64_END_CENTRAL_LOC_HDR_SIG ;
+
+    my $total_disks = unpack("V", substr($data, 16, 4)) ;
+
+    if ($total_disks == 1)
+    {
+        print "Nothing to do: 'Total Number of Disks' field is already 1\n";
+        next
+    }
+
+    if ($total_disks != 0)
+    {
+        die "Error: 'Total Number of Disks' field is $total_disks\n";
+    }
+
+    Seek $filename, $fh, -42 + 16 ;
+
+    if ($dry_run)
+    {
+        print "Dry-Run: No change made to '$filename'\n";
+    }
+    else
+    {
+        print $fh pack "V", 1 ;
+        print "Updated '$filename'\n";
+    }
+}
+
+sub Seek
+{
+    my $filename = shift;
+    my $fh = shift ;
+    my $offset = shift ;
+
+    seek $fh, $offset, SEEK_END
+        or die "Cannot seek '$filename': $!\n" ;
+}
+
+sub Read
+{
+    my $filename = shift ;
+    my $fh = shift;
+    my $size = shift ;
+
+    my $data ;
+
+    read($fh, $data, $size) == $size
+        or die "Cannot read from '$filename': $!\n" ;
+
+    return $data;
+}
+
+sub Usage
+{
+    print <<'EOM';
+Usage: fix-onedrive-zip [--dry-run] file1.zip [file2.zip...]
+
+Fix OneDrive/Windows Zip files larger than 4Gig that have an invalid 'Total
+Number of Disks' field in the 'ZIP64 End Central Directory Locator'. The
+value in this field should be 1, but OneDrive/Windows sets it to 0. This
+makes it difficult to work with these files using standard unzip utilities.
+
+This program changes the 'Total Number of Disks' field value to 1.
+
+See https://github.com/pmqs/Fix-OneDrive-Zip for support.
+
+Copyright (c) 2020-2023 Paul Marquess (pmqs@cpan.org). All rights reserved.
+
+This program is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.
+
+EOM
+    exit;
+}