fix-onedrive-zip 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. #!/usr/bin/env perl
  2. # fix-onedrive-zip
  3. #
  4. # Fix OneDrive/Windows Zip files larger than 4Gig that have an invalid
  5. # 'Total Number of Disks' field in the 'ZIP64 End Central Directory
  6. # Locator'. The value in this field should be 1, but OneDrive/Windows sets
  7. # it to 0. This makes it difficult to work with these files using standard
  8. # unzip utilities.
  9. #
  10. # This program changes the 'Total Number of Disks' field value to 1.
  11. #
  12. # Copyright (c) 2020-2023 Paul Marquess. All rights reserved.
  13. #
  14. # This program is free software; you can redistribute it and/or modify it
  15. # under the same terms as Perl itself.
  16. use strict;
  17. use warnings;
  18. use Fcntl qw(SEEK_SET SEEK_END);
  19. use Getopt::Long;
  20. # Signatures for the headers we need to check
  21. use constant ZIP_LOCAL_HDR_SIG => 0x04034b50;
  22. use constant ZIP_END_CENTRAL_HDR_SIG => 0x06054b50;
  23. use constant ZIP64_END_CENTRAL_LOC_HDR_SIG => 0x07064b50;
  24. sub Seek;
  25. sub Read;
  26. my $VERSION = '1.02' ;
  27. my $dry_run ;
  28. BEGIN {
  29. # Check for a 32-bit Perl
  30. if (!eval { pack "Q", 1 }) {
  31. warn "The perl executable you are running is 32-bit.\n" .
  32. "You need to install a 64-bit perl executable to continue\n";
  33. exit(1);
  34. }
  35. }
  36. GetOptions ("dry-run" => \$dry_run,
  37. "help" => \&Usage)
  38. or Usage("Error in command line arguments\n");
  39. Usage()
  40. unless @ARGV >= 1;
  41. for my $filename (@ARGV)
  42. {
  43. open my $fh, "+<$filename"
  44. or die "Error: Cannot open '$filename': $!\n";
  45. }
  46. for my $filename (@ARGV)
  47. {
  48. print "\nChecking '$filename'\n";
  49. open my $fh, "+<$filename"
  50. or die "Cannot open '$filename': $!\n";
  51. my $data = Read $filename, $fh, 4;
  52. my $sig = unpack("V", $data) ;
  53. die "Error: No Zip signature at start of '$filename'\n"
  54. unless $sig == ZIP_LOCAL_HDR_SIG ;
  55. # Assume no comment or other trailing data
  56. # The last two things in the file are the Z64 & EOCD records
  57. Seek $filename, $fh, -42 ;
  58. $data = Read $filename, $fh, 42;
  59. my $eocd = substr($data, 20);
  60. my $eocd_sig = unpack("V", substr($eocd, 0, 4)) ;
  61. die "Error: Cannot find Zip signature at end of '$filename'\n"
  62. unless $eocd_sig == ZIP_END_CENTRAL_HDR_SIG ;
  63. my $offset = unpack("VV", substr($eocd, 16, 4)) ;
  64. die sprintf "Error: bad offset 0x%X at end of '$filename'\n", $offset
  65. unless $offset == 0xFFFFFFFF ;
  66. my $z64_sig = unpack("V", substr($data, 0, 4)) ;
  67. die "Error: Cannot find Zip64 signature at end of '$filename'\n"
  68. unless $z64_sig == ZIP64_END_CENTRAL_LOC_HDR_SIG ;
  69. my $total_disks = unpack("V", substr($data, 16, 4)) ;
  70. if ($total_disks == 1)
  71. {
  72. print "Nothing to do: 'Total Number of Disks' field is already 1\n";
  73. next
  74. }
  75. if ($total_disks != 0)
  76. {
  77. die "Error: 'Total Number of Disks' field is $total_disks\n";
  78. }
  79. Seek $filename, $fh, -42 + 16 ;
  80. if ($dry_run)
  81. {
  82. print "Dry-Run: No change made to '$filename'\n";
  83. }
  84. else
  85. {
  86. print $fh pack "V", 1 ;
  87. print "Updated '$filename'\n";
  88. }
  89. }
  90. sub Seek
  91. {
  92. my $filename = shift;
  93. my $fh = shift ;
  94. my $offset = shift ;
  95. seek $fh, $offset, SEEK_END
  96. or die "Cannot seek '$filename': $!\n" ;
  97. }
  98. sub Read
  99. {
  100. my $filename = shift ;
  101. my $fh = shift;
  102. my $size = shift ;
  103. my $data ;
  104. read($fh, $data, $size) == $size
  105. or die "Cannot read from '$filename': $!\n" ;
  106. return $data;
  107. }
  108. sub Usage
  109. {
  110. print <<'EOM';
  111. Usage: fix-onedrive-zip [--dry-run] file1.zip [file2.zip...]
  112. Fix OneDrive/Windows Zip files larger than 4Gig that have an invalid 'Total
  113. Number of Disks' field in the 'ZIP64 End Central Directory Locator'. The
  114. value in this field should be 1, but OneDrive/Windows sets it to 0. This
  115. makes it difficult to work with these files using standard unzip utilities.
  116. This program changes the 'Total Number of Disks' field value to 1.
  117. See https://github.com/pmqs/Fix-OneDrive-Zip for support.
  118. Copyright (c) 2020-2023 Paul Marquess (pmqs@cpan.org). All rights reserved.
  119. This program is free software; you can redistribute it and/or modify it
  120. under the same terms as Perl itself.
  121. EOM
  122. exit;
  123. }