17
17
package org .springframework .boot .loader .jar ;
18
18
19
19
import java .io .IOException ;
20
+ import java .util .Optional ;
20
21
21
22
import org .springframework .boot .loader .data .RandomAccessData ;
22
23
25
26
*
26
27
* @author Phillip Webb
27
28
* @author Andy Wilkinson
29
+ * @author Camille Vienot
28
30
* @see <a href="https://en.wikipedia.org/wiki/Zip_%28file_format%29">Zip File Format</a>
29
31
*/
30
32
class CentralDirectoryEndRecord {
@@ -33,6 +35,8 @@ class CentralDirectoryEndRecord {
33
35
34
36
private static final int MAXIMUM_COMMENT_LENGTH = 0xFFFF ;
35
37
38
+ private static final int ZIP64_MAGICCOUNT = 0xFFFF ;
39
+
36
40
private static final int MAXIMUM_SIZE = MINIMUM_SIZE + MAXIMUM_COMMENT_LENGTH ;
37
41
38
42
private static final int SIGNATURE = 0x06054b50 ;
@@ -41,6 +45,8 @@ class CentralDirectoryEndRecord {
41
45
42
46
private static final int READ_BLOCK_SIZE = 256 ;
43
47
48
+ private final Optional <Zip64End > zip64End ;
49
+
44
50
private byte [] block ;
45
51
46
52
private int offset ;
@@ -69,6 +75,9 @@ class CentralDirectoryEndRecord {
69
75
}
70
76
this .offset = this .block .length - this .size ;
71
77
}
78
+ int startOfCentralDirectoryEndRecord = (int ) (data .getSize () - this .size );
79
+ this .zip64End = Optional .ofNullable (
80
+ isZip64 () ? new Zip64End (data , startOfCentralDirectoryEndRecord ) : null );
72
81
}
73
82
74
83
private byte [] createBlockFromEndOfData (RandomAccessData data , int size ) throws IOException {
@@ -95,7 +104,10 @@ private boolean isValid() {
95
104
long getStartOfArchive (RandomAccessData data ) {
96
105
long length = Bytes .littleEndianValue (this .block , this .offset + 12 , 4 );
97
106
long specifiedOffset = Bytes .littleEndianValue (this .block , this .offset + 16 , 4 );
98
- long actualOffset = data .getSize () - this .size - length ;
107
+ long zip64EndSize = this .zip64End .map ((x ) -> x .getSize ()).orElse (0L );
108
+ int zip64LocSize = this .zip64End .map ((x ) -> Zip64Locator .ZIP64_LOCSIZE ).orElse (0 );
109
+ long actualOffset = data .getSize () - this .size - length - zip64EndSize
110
+ - zip64LocSize ;
99
111
return actualOffset - specifiedOffset ;
100
112
}
101
113
@@ -106,21 +118,136 @@ long getStartOfArchive(RandomAccessData data) {
106
118
* @return the central directory data
107
119
*/
108
120
RandomAccessData getCentralDirectory (RandomAccessData data ) {
109
- long offset = Bytes .littleEndianValue (this .block , this .offset + 16 , 4 );
110
- long length = Bytes .littleEndianValue (this .block , this .offset + 12 , 4 );
111
- return data .getSubsection (offset , length );
121
+ if (isZip64 ()) {
122
+ return this .zip64End .get ().getCentratDirectory (data );
123
+ }
124
+ else {
125
+ long offset = Bytes .littleEndianValue (this .block , this .offset + 16 , 4 );
126
+ long length = Bytes .littleEndianValue (this .block , this .offset + 12 , 4 );
127
+ return data .getSubsection (offset , length );
128
+ }
112
129
}
113
130
114
131
/**
115
132
* Return the number of ZIP entries in the file.
116
133
* @return the number of records in the zip
117
134
*/
118
135
int getNumberOfRecords () {
119
- long numberOfRecords = Bytes .littleEndianValue (this .block , this .offset + 10 , 2 );
120
- if (numberOfRecords == 0xFFFF ) {
121
- throw new IllegalStateException ("Zip64 archives are not supported" );
136
+ if (isZip64 ()) {
137
+ return this .zip64End .get ().getNumberOfRecords ();
138
+ }
139
+ else {
140
+ long numberOfRecords = Bytes .littleEndianValue (this .block , this .offset + 10 ,
141
+ 2 );
142
+ return (int ) numberOfRecords ;
143
+ }
144
+ }
145
+
146
+ boolean isZip64 () {
147
+ return (int ) Bytes .littleEndianValue (this .block , this .offset + 10 ,
148
+ 2 ) == ZIP64_MAGICCOUNT ;
149
+ }
150
+
151
+ /**
152
+ * A Zip64 end of central directory record.
153
+ *
154
+ * @see <a href="https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT">Chapter
155
+ * 4.3.14 of Zip64 specification</a>
156
+ */
157
+ private static class Zip64End {
158
+
159
+ static final int ZIP64_ENDTOT = 32 ; // total number of entries
160
+ static final int ZIP64_ENDSIZ = 40 ; // central directory size in bytes
161
+ static final int ZIP64_ENDOFF = 48 ; // offset of first CEN header
162
+
163
+ private final Zip64Locator locator ;
164
+
165
+ private final long centralDirectoryOffset ;
166
+
167
+ private final long centralDirectoryLength ;
168
+
169
+ private int numberOfRecords ;
170
+
171
+ Zip64End (RandomAccessData data , int centratDirectoryEndOffset )
172
+ throws IOException {
173
+ this (data , new Zip64Locator (data , centratDirectoryEndOffset ));
174
+ }
175
+
176
+ Zip64End (RandomAccessData data , Zip64Locator locator ) throws IOException {
177
+ this .locator = locator ;
178
+ byte [] block = data .read (locator .getZip64EndOffset (), 56 );
179
+ this .centralDirectoryOffset = Bytes .littleEndianValue (block , ZIP64_ENDOFF , 8 );
180
+ this .centralDirectoryLength = Bytes .littleEndianValue (block , ZIP64_ENDSIZ , 8 );
181
+ this .numberOfRecords = (int ) Bytes .littleEndianValue (block , ZIP64_ENDTOT , 8 );
122
182
}
123
- return (int ) numberOfRecords ;
183
+
184
+ /**
185
+ * Return the size of this zip 64 end of central directory record.
186
+ * @return size of this zip 64 end of central directory record
187
+ */
188
+ public long getSize () {
189
+ return this .locator .getZip64EndSize ();
190
+ }
191
+
192
+ /**
193
+ * Return the bytes of the "Central directory" based on the offset indicated in
194
+ * this record.
195
+ * @param data the source data
196
+ * @return the central directory data
197
+ */
198
+ public RandomAccessData getCentratDirectory (RandomAccessData data ) {
199
+ return data .getSubsection (this .centralDirectoryOffset ,
200
+ this .centralDirectoryLength );
201
+ }
202
+
203
+ /**
204
+ * Return the number of entries in the zip64 archive.
205
+ * @return the number of records in the zip
206
+ */
207
+ public int getNumberOfRecords () {
208
+ return this .numberOfRecords ;
209
+ }
210
+
211
+ }
212
+
213
+ /**
214
+ * A Zip64 end of central directory locator.
215
+ *
216
+ * @see <a href="https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT">Chapter
217
+ * 4.3.15 of Zip64 specification</a>
218
+ */
219
+ private static class Zip64Locator {
220
+
221
+ static final int ZIP64_LOCSIZE = 20 ; // locator size
222
+ static final int ZIP64_LOCOFF = 8 ; // offset of zip64 end
223
+
224
+ private final long zip64EndOffset ;
225
+
226
+ private final int offset ;
227
+
228
+ Zip64Locator (RandomAccessData data , int centralDirectoryEndOffset )
229
+ throws IOException {
230
+ this .offset = centralDirectoryEndOffset - ZIP64_LOCSIZE ;
231
+ byte [] block = data .read (this .offset , ZIP64_LOCSIZE );
232
+ this .zip64EndOffset = Bytes .littleEndianValue (block , ZIP64_LOCOFF , 8 );
233
+ }
234
+
235
+ /**
236
+ * Return the size of the zip 64 end record located by this zip64 end locator.
237
+ * @return size of the zip 64 end record located by this zip64 end locator
238
+ */
239
+ public long getZip64EndSize () {
240
+ return this .offset - this .zip64EndOffset ;
241
+ }
242
+
243
+ /**
244
+ * Return the offset to locate {@link Zip64End}.
245
+ * @return offset of the Zip64 end of central directory record
246
+ */
247
+ public long getZip64EndOffset () {
248
+ return this .zip64EndOffset ;
249
+ }
250
+
124
251
}
125
252
126
253
String getComment () {
0 commit comments