@@ -907,6 +907,146 @@ public static async Task ZipArchive_InvalidHuffmanData()
907
907
}
908
908
}
909
909
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
+
910
1050
private static readonly byte [ ] s_slightlyIncorrectZip64 =
911
1051
{
912
1052
// ===== Local file header signature 0x04034b50
@@ -925,7 +1065,7 @@ public static async Task ZipArchive_InvalidHuffmanData()
925
1065
0x0c , 0x7e , 0x7f , 0xd8 ,
926
1066
// compressed size
927
1067
0xff , 0xff , 0xff , 0xff ,
928
- // UNcompressed size
1068
+ // uncompressed size
929
1069
0xff , 0xff , 0xff , 0xff ,
930
1070
// file name length
931
1071
0x08 , 0x00 ,
@@ -976,7 +1116,7 @@ public static async Task ZipArchive_InvalidHuffmanData()
976
1116
0x0c , 0x7e , 0x7f , 0xd8 ,
977
1117
// 4 byte compressed size, index 120 (-1 indicates refer to Zip64 extra field)
978
1118
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)
980
1120
0xff , 0xff , 0xff , 0xff ,
981
1121
// file name length
982
1122
0x08 , 0x00 ,
@@ -1066,7 +1206,7 @@ public static async Task ZipArchive_InvalidHuffmanData()
1066
1206
0x0c , 0x7e , 0x7f , 0xd8 ,
1067
1207
// compressed size
1068
1208
0xff , 0xff , 0xff , 0xff ,
1069
- // UNcompressed size
1209
+ // uncompressed size
1070
1210
0xff , 0xff , 0xff , 0xff ,
1071
1211
// file name length
1072
1212
@@ -1079,7 +1219,7 @@ public static async Task ZipArchive_InvalidHuffmanData()
1079
1219
0x01 , 0x00 ,
1080
1220
// size of extra field block
1081
1221
0x20 , 0x00 ,
1082
- // 8 byte Zip64 UNcompressed size, index 42
1222
+ // 8 byte Zip64 uncompressed size, index 42
1083
1223
0x04 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
1084
1224
// 8 byte Zip64 compressed size, index 50
1085
1225
0x06 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
@@ -1122,7 +1262,7 @@ public static async Task ZipArchive_InvalidHuffmanData()
1122
1262
0x0c , 0x7e , 0x7f , 0xd8 ,
1123
1263
// 4 byte compressed size, index 120 (-1 indicates refer to Zip64 extra field)
1124
1264
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)
1126
1266
0xff , 0xff , 0xff , 0xff ,
1127
1267
// file name length
1128
1268
0x08 , 0x00 ,
0 commit comments