@@ -907,6 +907,146 @@ public static async Task ZipArchive_InvalidHuffmanData()
907907 }
908908 }
909909
910+ [ Fact ]
911+ public static void ZipArchive_InvalidVersionToExtract ( )
912+ {
913+ using ( MemoryStream updatedStream = new MemoryStream ( ) )
914+ {
915+ int originalLocalVersionToExtract = s_inconsistentVersionToExtract [ 4 ] ;
916+ int originalCentralDirectoryVersionToExtract = s_inconsistentVersionToExtract [ 57 ] ;
917+
918+ // The existing archive will have a "version to extract" of 0.0, but will contain entries
919+ // with deflate compression (which has a minimum version to extract of 2.0.)
920+ Assert . Equal ( 0x00 , originalLocalVersionToExtract ) ;
921+ Assert . Equal ( 0x00 , originalCentralDirectoryVersionToExtract ) ;
922+
923+ // Write the example data to the stream. We expect to be able to read it (and the entry contents) successfully.
924+ updatedStream . Write ( s_inconsistentVersionToExtract ) ;
925+ updatedStream . Seek ( 0 , SeekOrigin . Begin ) ;
926+
927+ using ( ZipArchive originalArchive = new ZipArchive ( updatedStream , ZipArchiveMode . Read , true ) )
928+ {
929+ Assert . Equal ( 1 , originalArchive . Entries . Count ) ;
930+
931+ ZipArchiveEntry firstEntry = originalArchive . Entries [ 0 ] ;
932+
933+ Assert . Equal ( "first.bin" , firstEntry . Name ) ;
934+ Assert . Equal ( 10 , firstEntry . Length ) ;
935+
936+ using ( Stream entryStream = firstEntry . Open ( ) )
937+ {
938+ Assert . Equal ( 10 , firstEntry . Length ) ;
939+
940+ byte [ ] uncompressedBytes = new byte [ firstEntry . Length ] ;
941+ int bytesRead = entryStream . Read ( uncompressedBytes ) ;
942+
943+ Assert . Equal ( 10 , bytesRead ) ;
944+
945+ Assert . Equal ( new byte [ ] { 0x00 , 0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 , 0x07 , 0x08 , 0x09 } , uncompressedBytes ) ;
946+ }
947+ }
948+
949+ updatedStream . Seek ( 0 , SeekOrigin . Begin ) ;
950+
951+ // Create a new entry, forcing the central directory headers to be rewritten. The local file header
952+ // for first.bin would normally be skipped (because it hasn't changed) but it needs to be rewritten
953+ // because the central directory headers will be rewritten with a valid value and the local file header
954+ // needs to match.
955+ using ( ZipArchive updatedArchive = new ZipArchive ( updatedStream , ZipArchiveMode . Update ) )
956+ {
957+ ZipArchiveEntry newEntry = updatedArchive . CreateEntry ( "second.bin" , CompressionLevel . NoCompression ) ;
958+ }
959+
960+ byte [ ] updatedContents = updatedStream . ToArray ( ) ;
961+ int updatedLocalVersionToExtract = updatedContents [ 4 ] ;
962+ int updatedCentralDirectoryVersionToExtract = updatedContents [ 97 ] ;
963+
964+ Assert . Equal ( 20 , updatedCentralDirectoryVersionToExtract ) ;
965+ Assert . Equal ( 20 , updatedLocalVersionToExtract ) ;
966+ }
967+ }
968+
969+ private static readonly byte [ ] s_inconsistentVersionToExtract =
970+ {
971+ // ===== Local file header signature 0x04034b50
972+ 0x50 , 0x4b , 0x03 , 0x04 ,
973+ // version to extract 0.0 (invalid - this should be at least 2.0 to make use of deflate compression)
974+ 0x00 , 0x00 ,
975+ // general purpose flags
976+ 0x02 , 0x00 , // 0000_0002 'for maximum-compression deflating'
977+ // Deflate
978+ 0x08 , 0x00 ,
979+ // Last mod file time
980+ 0x3b , 0x33 ,
981+ // Last mod date
982+ 0x3f , 0x5a ,
983+ // CRC32
984+ 0x46 , 0xd7 , 0x6c , 0x45 ,
985+ // compressed size
986+ 0x0c , 0x00 , 0x00 , 0x00 ,
987+ // uncompressed size
988+ 0x0a , 0x00 , 0x00 , 0x00 ,
989+ // file name length
990+ 0x09 , 0x00 ,
991+ // extra field length
992+ 0x00 , 0x00 ,
993+ // filename
994+ 0x66 , 0x69 , 0x72 , 0x73 , 0x74 , 0x2e , 0x62 , 0x69 , 0x6e ,
995+ // -------------
996+ // Data!
997+ 0x63 , 0x60 , 0x64 , 0x62 , 0x66 , 0x61 , 0x65 , 0x63 , 0xe7 , 0xe0 , 0x04 , 0x00 ,
998+ // -------- Central directory signature 0x02014b50
999+ 0x50 , 0x4b , 0x01 , 0x02 ,
1000+ // version made by 2.0
1001+ 0x14 , 0x00 ,
1002+ // version to extract 0.0 (invalid - this should be at least 2.0 to make use of deflate compression)
1003+ 0x00 , 0x00 ,
1004+ // general purpose flags
1005+ 0x02 , 0x00 ,
1006+ // Deflate
1007+ 0x08 , 0x00 ,
1008+ // Last mod file time
1009+ 0x3b , 0x33 ,
1010+ // Last mod date
1011+ 0x3f , 0x5a ,
1012+ // CRC32
1013+ 0x46 , 0xd7 , 0x6c , 0x45 ,
1014+ // compressed size
1015+ 0x0c , 0x00 , 0x00 , 0x00 ,
1016+ // uncompressed size
1017+ 0x0a , 0x00 , 0x00 , 0x00 ,
1018+ // file name length
1019+ 0x09 , 0x00 ,
1020+ // extra field length
1021+ 0x00 , 0x00 ,
1022+ // file comment length
1023+ 0x00 , 0x00 ,
1024+ // disk number start
1025+ 0x00 , 0x00 ,
1026+ // internal file attributes
1027+ 0x00 , 0x00 ,
1028+ // external file attributes
1029+ 0x00 , 0x00 , 0x00 , 0x00 ,
1030+ // relative offset of local header
1031+ 0x00 , 0x00 , 0x00 , 0x00 ,
1032+ // file name
1033+ 0x66 , 0x69 , 0x72 , 0x73 , 0x74 , 0x2e , 0x62 , 0x69 , 0x6e ,
1034+ // == 'end of CD' signature 0x06054b50
1035+ 0x50 , 0x4b , 0x05 , 0x06 ,
1036+ // disk number, disk number with CD
1037+ 0x00 , 0x00 ,
1038+ 0x00 , 0x00 ,
1039+ // total number of entries in CD on this disk, and overall
1040+ 0x01 , 0x00 ,
1041+ 0x01 , 0x00 ,
1042+ // size of CD
1043+ 0x37 , 0x00 , 0x00 , 0x00 ,
1044+ // offset of start of CD wrt start disk
1045+ 0x33 , 0x00 , 0x00 , 0x00 ,
1046+ // comment length
1047+ 0x00 , 0x00
1048+ } ;
1049+
9101050 private static readonly byte [ ] s_slightlyIncorrectZip64 =
9111051 {
9121052 // ===== Local file header signature 0x04034b50
@@ -925,7 +1065,7 @@ public static async Task ZipArchive_InvalidHuffmanData()
9251065 0x0c , 0x7e , 0x7f , 0xd8 ,
9261066 // compressed size
9271067 0xff , 0xff , 0xff , 0xff ,
928- // UNcompressed size
1068+ // uncompressed size
9291069 0xff , 0xff , 0xff , 0xff ,
9301070 // file name length
9311071 0x08 , 0x00 ,
@@ -976,7 +1116,7 @@ public static async Task ZipArchive_InvalidHuffmanData()
9761116 0x0c , 0x7e , 0x7f , 0xd8 ,
9771117 // 4 byte compressed size, index 120 (-1 indicates refer to Zip64 extra field)
9781118 0xff , 0xff , 0xff , 0xff ,
979- // 4 byte UNcompressed size, index 124 (-1 indicates refer to Zip64 extra field)
1119+ // 4 byte uncompressed size, index 124 (-1 indicates refer to Zip64 extra field)
9801120 0xff , 0xff , 0xff , 0xff ,
9811121 // file name length
9821122 0x08 , 0x00 ,
@@ -1066,7 +1206,7 @@ public static async Task ZipArchive_InvalidHuffmanData()
10661206 0x0c , 0x7e , 0x7f , 0xd8 ,
10671207 // compressed size
10681208 0xff , 0xff , 0xff , 0xff ,
1069- // UNcompressed size
1209+ // uncompressed size
10701210 0xff , 0xff , 0xff , 0xff ,
10711211 // file name length
10721212
@@ -1079,7 +1219,7 @@ public static async Task ZipArchive_InvalidHuffmanData()
10791219 0x01 , 0x00 ,
10801220 // size of extra field block
10811221 0x20 , 0x00 ,
1082- // 8 byte Zip64 UNcompressed size, index 42
1222+ // 8 byte Zip64 uncompressed size, index 42
10831223 0x04 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
10841224 // 8 byte Zip64 compressed size, index 50
10851225 0x06 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
@@ -1122,7 +1262,7 @@ public static async Task ZipArchive_InvalidHuffmanData()
11221262 0x0c , 0x7e , 0x7f , 0xd8 ,
11231263 // 4 byte compressed size, index 120 (-1 indicates refer to Zip64 extra field)
11241264 0xff , 0xff , 0xff , 0xff ,
1125- // 4 byte UNcompressed size, index 124 (-1 indicates refer to Zip64 extra field)
1265+ // 4 byte uncompressed size, index 124 (-1 indicates refer to Zip64 extra field)
11261266 0xff , 0xff , 0xff , 0xff ,
11271267 // file name length
11281268 0x08 , 0x00 ,
0 commit comments