13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package org.openjdk.jigsaw;
27
28 import java.io.*;
29 import java.nio.charset.Charset;
30 import java.security.*;
31 import java.util.*;
32 import java.util.jar.*;
33 import java.util.zip.*;
34
35 import static org.openjdk.jigsaw.FileConstants.ModuleFile.*;
36
37 public final class ModuleFile {
38 /**
39 * Return the subdir of a section in an extracted module file.
40 */
41 public static String getSubdirOfSection(SectionType type) {
42 switch (type) {
43 case MODULE_INFO:
44 case SIGNATURE:
45 return ".";
46 case CLASSES:
47 case RESOURCES:
48 return "classes";
49 case NATIVE_LIBS:
50 return "lib";
51 case NATIVE_CMDS:
52 return "bin";
53 case CONFIG:
54 return "etc";
55 default:
56 throw new AssertionError(type);
57 }
58 }
59
60 public final static class Reader implements Closeable {
61
62 private DataInputStream stream;
63 private File destination;
64 private boolean deflate;
65 private final HashType hashtype = HashType.SHA256;
66 private File natlibs;
67 private File natcmds;
68 private File configs;
69
70 private static class CountingInputStream extends FilterInputStream {
71 int count;
72 public CountingInputStream(InputStream stream, int count) {
73 super(stream);
74 this.count = count;
75 }
76
77 public int available() throws IOException {
78 return count;
79 }
80
81 public boolean markSupported() {
82 return false;
83 }
84
85 public int read() throws IOException {
86 if (count == 0)
87 return -1;
88 int read = super.read();
89 if (-1 != read)
90 count--;
91 return read;
92 }
93
94 public int read(byte[] b, int off, int len) throws IOException {
95 if (count == 0)
96 return -1;
97 len = Math.min(len, count);
98 int read = super.read(b, off, len);
99 if (-1 != read)
100 count-=read;
101 return read;
102 }
103
104 public void reset() throws IOException {
105 throw new IOException("Can't reset this stream");
106 }
107
108 public long skip(long n) throws IOException {
109 if (count == 0)
110 return -1;
111 n = Math.min(n, count);
112 long skipped = super.skip(n);
113 if (n > 0)
114 count-=skipped;
115 return skipped;
116 }
117 }
118
119 public Reader(DataInputStream stream) {
120 // Ensure that mark/reset is supported
121 if (stream.markSupported()) {
122 this.stream = stream;
123 } else {
124 this.stream =
125 new DataInputStream(new BufferedInputStream(stream));
126 }
127 }
128
129 private void checkHashMatch(byte[] expected, byte[] computed)
130 throws IOException
131 {
132 if (!MessageDigest.isEqual(expected, computed))
133 throw new IOException("Expected hash "
134 + hashHexString(expected)
135 + " instead of "
136 + hashHexString(computed));
137 }
138
139 private ModuleFileHeader fileHeader = null;
140 private MessageDigest fileDigest = null;
141 private MessageDigest sectionDigest = null;
142 private DataInputStream fileIn = null;
143 private byte[] moduleInfoBytes = null;
144 private SignatureType moduleSignatureType = null;
145 private byte[] moduleSignatureBytes = null;
146 private final int MAX_SECTION_HEADER_LENGTH = 128;
147 private List<byte[]> calculatedHashes = new ArrayList<>();
148 private boolean extract = true;
149
150 /*
151 * Reads the MODULE_INFO section and the Signature section, if present,
152 * but does not write any files.
153 */
154 public byte[] readStart() throws IOException {
155
156 try {
157 fileDigest = getHashInstance(hashtype);
158 sectionDigest = getHashInstance(hashtype);
159 DigestInputStream dis =
160 new DigestInputStream(stream, fileDigest);
161 fileHeader = ModuleFileHeader.read(dis);
162 // calculate module header hash
163 ByteArrayOutputStream baos = new ByteArrayOutputStream();
164 fileHeader.write(new DataOutputStream(baos));
165 sectionDigest.update(baos.toByteArray());
166 calculatedHashes.add(sectionDigest.digest());
167
168 fileIn = new DataInputStream(dis);
169 if (readSection(fileIn) != SectionType.MODULE_INFO)
170 throw new IOException("First module-file section"
171 + " is not MODULE_INFO");
172 assert moduleInfoBytes != null;
173
174 // Read the Signature Section, if present
175 readSignatureSection(fileIn, dis);
176
177 return moduleInfoBytes.clone();
178 } catch (IOException x) {
179 close();
180 throw x;
181 }
182 }
183
184 public void readRest() throws IOException {
185 extract = false;
186 readRest(null, false, null, null, null);
187 }
188
189 public void readRest(File dst, boolean deflate) throws IOException {
190 readRest(dst, deflate, null, null, null);
191 }
192
193 public void readRest(File dst, boolean deflate, File natlibs,
194 File natcmds, File configs)
195 throws IOException
196 {
197 this.deflate = deflate;
198 this.destination = dst != null ? dst.getCanonicalFile() : null;
199 this.natlibs = natlibs != null ? natlibs : new File(destination, "lib");
200 this.natcmds = natcmds != null ? natcmds : new File(destination, "bin");
201 this.configs = configs != null ? configs : new File(destination, "etc");
202 try {
203 if (extract)
204 Files.store(moduleInfoBytes, computeRealPath("info"));
205 // Module-Info and Signature, if present, have been consumed
206
207 // Read rest of file until all sections have been read
208 stream.mark(1);
209 while (-1 != stream.read()) {
210 stream.reset();
211 readSection(fileIn);
212 stream.mark(1);
213 }
214
215 close();
216 byte[] fileHeaderHash = fileHeader.getHashNoClone();
217 checkHashMatch(fileHeaderHash, fileDigest.digest());
218 calculatedHashes.add(fileHeaderHash);
219 } finally {
220 close();
221 }
222 }
223
224 public byte[] getHash() throws IOException {
225 if (null == fileHeader)
226 readStart();
227 return fileHeader.getHash();
228 }
229
230 public List<byte[]> getCalculatedHashes() {
231 return calculatedHashes;
232 }
233
234 public boolean hasSignature() throws IOException {
235 if (null == fileHeader)
236 readStart();
237 return moduleSignatureBytes != null;
238 }
239
240 public SignatureType getSignatureType() throws IOException {
241 if (null == fileHeader)
242 readStart();
243 return moduleSignatureType;
244 }
245
246 public byte[] getSignature() throws IOException {
247 if (null == fileHeader)
248 readStart();
249 return moduleSignatureBytes != null
250 ? moduleSignatureBytes.clone()
251 : null;
252 }
253
254 byte[] getSignatureNoClone() {
255 return moduleSignatureBytes;
256 }
257
258 private JarOutputStream contentStream = null;
259
260 private JarOutputStream contentStream() throws IOException {
261 if (contentStream == null) {
262 if (extract) {
263 FileOutputStream fos
264 = new FileOutputStream(computeRealPath("classes"));
265 contentStream
266 = new JarOutputStream(new BufferedOutputStream(fos));
267 } else {
268 contentStream = new JarOutputStream(new NullOutputStream());
269 }
270 }
271 return contentStream;
272 }
273
274 public void close() throws IOException {
275 try {
276 try {
277 if (contentStream != null) {
278 contentStream.close();
279 contentStream = null;
280 }
281 } finally {
282 if (fileIn != null) {
283 fileIn.close();
284 fileIn = null;
285 }
286 }
287 } finally {
288 if (filesWriter != null) {
289 filesWriter.close();
290 filesWriter = null;
291 }
292 }
293 }
294
295 public void readModule() throws IOException {
296 extract = false;
297 readStart();
298 readRest();
299 }
300
301 public void readModule(File dst) throws IOException {
302 readStart();
303 readRest(dst, false);
304 }
305
306 private void readSignatureSection(DataInputStream stream,
307 DigestInputStream dis)
308 throws IOException
309 {
310
311 // Turn off digest computation before reading Signature Section
312 dis.on(false);
313
314 // Mark the starting position
315 stream.mark(MAX_SECTION_HEADER_LENGTH);
316 if (stream.read() != -1) {
317 stream.reset();
318 SectionHeader header = SectionHeader.read(stream);
319 if (header != null &&
320 header.getType() == SectionType.SIGNATURE) {
321 readSectionContent(header, stream);
322 } else {
323 // Revert back to the starting position
324 stream.reset();
325 }
326 }
327
328 // Turn on digest computation again
329 dis.on(true);
330 }
331
332 private SectionType readSection(DataInputStream stream)
333 throws IOException
334 {
335 SectionHeader header = SectionHeader.read(stream);
336 readSectionContent(header, stream);
337 return header.getType();
338 }
339
340 private void readSectionContent(SectionHeader header,
341 DataInputStream stream)
342 throws IOException
343 {
344 SectionType type = header.getType();
345 Compressor compressor = header.getCompressor();
346 int csize = header.getCSize();
347 short subsections =
348 type.hasFiles() ? header.getSubsections() : 1;
349
350 CountingInputStream cs = new CountingInputStream(stream, csize);
351 sectionDigest.reset();
352 DigestInputStream dis = new DigestInputStream(cs, sectionDigest);
353 DataInputStream in = new DataInputStream(dis);
354
355 for (int subsection = 0; subsection < subsections; subsection++)
356 readFile(in, compressor, type, csize);
357
358 byte[] headerHash = header.getHashNoClone();
359 checkHashMatch(headerHash, sectionDigest.digest());
360 if (header.getType() != SectionType.SIGNATURE) {
361 calculatedHashes.add(headerHash);
362 }
363 }
364
365 public void readFile(DataInputStream in,
366 Compressor compressor,
367 SectionType type,
368 int csize)
369 throws IOException
370 {
371 switch (compressor) {
372 case NONE:
373 if (type == SectionType.MODULE_INFO) {
374 moduleInfoBytes = readModuleInfo(in, csize);
375
376 } else if (type == SectionType.SIGNATURE) {
377 // Examine the Signature header
378 int signatureTypeValue = (int)in.readShort();
379 try {
380 moduleSignatureType =
381 SignatureType.valueOf(signatureTypeValue);
382 } catch (IllegalArgumentException x) {
383 throw new IOException("Invalid signature type: " +
384 signatureTypeValue);
385 }
386 int length = in.readInt();
387 moduleSignatureBytes = readModuleSignature(in, csize - 6);
388 if (length != moduleSignatureBytes.length) {
389 throw new IOException("Invalid Signature length");
390 }
391 } else {
392 readUncompressedFile(in, type, csize);
393 }
394 break;
395 case GZIP:
396 readGZIPCompressedFile(in, type);
397 break;
398 case PACK200_GZIP:
399 readClasses(
400 new DataInputStream(new CountingInputStream(in, csize)));
401 break;
402 default:
403 throw new IOException("Unsupported Compressor for files: " +
404 compressor);
405 }
406 }
407
408 public void readClasses(DataInputStream in) throws IOException {
409 unpack200gzip(in);
410 }
411
412 private File currentPath = null;
413
414 private OutputStream openOutputStream(SectionType type,
415 String path)
416 throws IOException
417 {
418 if (!extract)
419 return new NullOutputStream();
420 currentPath = null;
421 assert type != SectionType.CLASSES;
422 if (type == SectionType.RESOURCES)
423 return Files.newOutputStream(contentStream(), path);
424 currentPath = computeRealPath(type, path);
425 File parent = currentPath.getParentFile();
426 if (!parent.exists())
427 Files.mkdirs(parent, currentPath.getName());
428 return new BufferedOutputStream(new FileOutputStream(currentPath));
429 }
430
431 private static class NullOutputStream extends OutputStream {
432 @Override
433 public void write(int b) throws IOException {}
434 @Override
435 public void write(byte[] b) throws IOException {}
436 @Override
437 public void write(byte[] b, int off, int len) throws IOException {}
438 }
439
440 public void readGZIPCompressedFile(DataInputStream in,
441 SectionType type)
442 throws IOException
443 {
444 SubSectionFileHeader header = SubSectionFileHeader.read(in);
445 int csize = header.getCSize();
446
447 // Splice off the compressed file from input stream
448 ByteArrayOutputStream baos = new ByteArrayOutputStream();
449 copyStream(new CountingInputStream(in, csize), baos, csize);
450
451 byte[] compressedfile = baos.toByteArray();
452 ByteArrayInputStream bain
453 = new ByteArrayInputStream(compressedfile);
454 try (GZIPInputStream gin = new GZIPInputStream(bain);
455 OutputStream out = openOutputStream(type, header.getPath())) {
456 copyStream(gin, out);
457 }
458
459 if (extract)
460 postExtract(type, currentPath);
461 }
462
463 public void readUncompressedFile(DataInputStream in,
464 SectionType type,
465 int csize)
466 throws IOException
467 {
468 assert type != SectionType.MODULE_INFO;
469 SubSectionFileHeader header = SubSectionFileHeader.read(in);
470 csize = header.getCSize();
471 try (OutputStream out = openOutputStream(type, header.getPath())) {
472 CountingInputStream cin = new CountingInputStream(in, csize);
473 byte[] buf = new byte[8192];
474 int n;
475 while ((n = cin.read(buf)) >= 0)
476 out.write(buf, 0, n);
477 }
478 if (extract) {
479 postExtract(type, currentPath);
480 }
481 }
482
483 public byte[] readModuleInfo(DataInputStream in, int csize)
484 throws IOException
485 {
486 CountingInputStream cin = new CountingInputStream(in, csize);
487 ByteArrayOutputStream out = new ByteArrayOutputStream();
488 byte[] buf = new byte[8192];
489 int n;
490 while ((n = cin.read(buf)) >= 0)
491 out.write(buf, 0, n);
492 return out.toByteArray();
493 }
494
495 public byte[] readModuleSignature(DataInputStream in, int csize)
496 throws IOException
497 {
498 return readModuleInfo(in, csize); // signature has the same format
499 }
500
501 // Track files installed outside the module library. For later removal.
502 // files are relative to the modules directory.
503 private PrintWriter filesWriter;
504
505 private void trackFiles(SectionType type, File file)
506 throws IOException
507 {
508 if (file == null || file.toPath().startsWith(destination.toPath()))
509 return;
510
511 // Lazy construction, not all modules will need this.
512 if (filesWriter == null)
513 filesWriter = new PrintWriter(computeRealPath("files"), "UTF-8");
514
515 filesWriter.println(Files.convertSeparator(relativize(destination, file)));
516 filesWriter.flush();
517 }
518
519 List<IOException> remove() {
520 return ModuleFile.Reader.remove(destination);
521 }
522
523 // Removes a module, given its module install directory
524 static List<IOException> remove(File moduleDir) {
525 List<IOException> excs = new ArrayList<>();
532 Charset.forName("UTF-8"));
533 for (String fn : filenames) {
534 try {
535 Files.delete(new File(moduleDir,
536 Files.platformSeparator(fn)));
537 } catch (IOException x) {
538 excs.add(x);
539 }
540 }
541 } catch (IOException x) {
542 excs.add(x);
543 }
544 }
545
546 excs.addAll(Files.deleteTreeUnchecked(moduleDir.toPath()));
547 return excs;
548 }
549
550 // Returns the absolute path of the given section type.
551 private File getDirOfSection(SectionType type) {
552 if (type == SectionType.NATIVE_LIBS)
553 return natlibs;
554 else if (type == SectionType.NATIVE_CMDS)
555 return natcmds;
556 else if (type == SectionType.CONFIG)
557 return configs;
558
559 // resolve sub dir section paths against the modules directory
560 return new File(destination, ModuleFile.getSubdirOfSection(type));
561 }
562
563 private File computeRealPath(String path) throws IOException {
564 return resolveAndNormalize(destination, path);
565 }
566
567 private File computeRealPath(SectionType type, String storedpath)
568 throws IOException
569 {
570 File sectionPath = getDirOfSection(type);
571 File realpath = new File(sectionPath,
572 Files.ensureNonAbsolute(Files.platformSeparator(storedpath)));
573
574 validatePath(sectionPath, realpath);
575
576 // Create the parent directories if necessary
577 File parent = realpath.getParentFile();
578 if (!parent.exists())
579 Files.mkdirs(parent, realpath.getName());
580
581 return realpath;
582 }
583
584 private static void markNativeCodeExecutable(SectionType type,
585 File file)
586 {
587 if (type == SectionType.NATIVE_CMDS
588 || (type == SectionType.NATIVE_LIBS
589 && System.getProperty("os.name").startsWith("Windows")))
590 {
591 file.setExecutable(true);
592 }
593 }
594
595 private void postExtract(SectionType type, File path)
596 throws IOException
597 {
598 markNativeCodeExecutable(type, path);
599 trackFiles(type, path);
600 }
601
602 private void unpack200gzip(DataInputStream in) throws IOException {
603 GZIPInputStream gis = new GZIPInputStream(in) {
604 public void close() throws IOException {}
605 };
606 Pack200.Unpacker unpacker = Pack200.newUnpacker();
607 if (deflate) {
608 Map<String,String> p = unpacker.properties();
609 p.put(Pack200.Unpacker.DEFLATE_HINT, Pack200.Unpacker.TRUE);
610 }
611 unpacker.unpack(gis, contentStream());
612 }
613
614 }
615
616 private static void checkCompressor(SectionType type,
617 Compressor compressor) {
618
619 if ((SectionType.MODULE_INFO == type &&
620 Compressor.NONE != compressor)
621 || (SectionType.CLASSES == type &&
622 Compressor.PACK200_GZIP != compressor))
623 throw new IllegalArgumentException(type
624 + " may not use compressor "
625 + compressor);
626 }
627
628 private static void checkSubsectionCount(SectionType type,
629 short subsections) {
630 if (!type.hasFiles() && subsections != 0)
631 throw new IllegalArgumentException(type
632 + " subsection count not 0: "
633 + subsections);
634 else if (type.hasFiles() && subsections == 0)
635 throw new IllegalArgumentException(type + " subsection count is 0");
636 }
637
638 private static void copyStream(InputStream in, DataOutput out)
639 throws IOException
640 {
641
642 byte[] buffer = new byte[1024 * 8];
643 for (int b_read = in.read(buffer);
644 -1 != b_read;
645 b_read = in.read(buffer))
646 out.write(buffer, 0, b_read);
647 }
648
649 private static void copyStream(InputStream in, OutputStream out)
650 throws IOException
651 {
652 copyStream(in, (DataOutput) new DataOutputStream(out));
653 }
654
655 private static void copyStream(InputStream in, DataOutput out,
656 int count)
657 throws IOException
658 {
659 byte[] buffer = new byte[1024 * 8];
660
661 while(count > 0) {
662 int b_read = in.read(buffer, 0, Math.min(count, buffer.length));
663 if (-1 == b_read)
664 return;
665 out.write(buffer, 0, b_read);
666 count-=b_read;
667 }
668 }
669
670 private static void copyStream(InputStream in, OutputStream out,
671 int count)
672 throws IOException
673 {
674 copyStream(in, (DataOutput) new DataOutputStream(out), count);
675 }
676
677 private static void ensureNonNegativity(long size, String parameter) {
678 if (size < 0)
679 throw new IllegalArgumentException(parameter + "<0: " + size);
680 }
681
682 private static void ensureNonNull(Object reference, String parameter) {
683 if (null == reference)
684 throw new IllegalArgumentException(parameter + " == null");
685 }
686
687 private static void ensureMatch(int found, int expected, String field)
688 throws IOException
689 {
690 if (found != expected)
691 throw new IOException(field + " expected : "
692 + Integer.toHexString(expected) + " found: "
693 + Integer.toHexString(found));
694 }
695
696 private static void ensureShortNativePath(File path, String name)
787
788 return hashLength;
789 }
790
791 private static byte[] readHashBytes(DataInputStream in, short hashLength)
792 throws IOException
793 {
794 final byte[] hash = new byte[hashLength];
795 in.readFully(hash);
796
797 return hash;
798 }
799
800 private static byte[] readHash(DataInputStream in) throws IOException {
801 return readHashBytes(in, readHashLength(in));
802 }
803
804 private static byte[] readFileHash(DigestInputStream dis)
805 throws IOException
806 {
807
808 DataInputStream in = new DataInputStream(dis);
809
810 final short hashLength = readHashLength(in);
811
812 // Turn digest computation off before reading the file hash
813 dis.on(false);
814 byte[] hash = readHashBytes(in, hashLength);
815 // Turn digest computation on again afterwards.
816 dis.on(true);
817
818 return hash;
819 }
820
821 public final static class ModuleFileHeader {
822 public static final int LENGTH_WITHOUT_HASH = 30;
823 public static final int LENGTH =
824 LENGTH_WITHOUT_HASH + HashType.SHA256.length();
825
826 // Fields are specified as unsigned. Treat signed values as bugs.
827 private final int magic; // MAGIC
1039 }
1040
1041 public void write(DataOutput out) throws IOException {
1042 out.writeShort(SubSectionType.FILE.value());
1043 out.writeInt(csize);
1044 out.writeUTF(path);
1045 }
1046
1047 public static SubSectionFileHeader read(DataInputStream in)
1048 throws IOException
1049 {
1050 final short type = in.readShort();
1051 ensureMatch(type, SubSectionType.FILE.value(),
1052 "ModuleFile.SubSectionType.FILE");
1053 final int csize = in.readInt();
1054 final String path = in.readUTF();
1055
1056 return new SubSectionFileHeader(csize, path);
1057 }
1058 }
1059
1060 private static void writeHash(DataOutput out, byte[] hash)
1061 throws IOException
1062 {
1063 out.writeShort(hash.length);
1064 out.write(hash);
1065 }
1066 }
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package org.openjdk.jigsaw;
27
28 import java.io.*;
29 import java.nio.charset.Charset;
30 import java.security.*;
31 import java.util.*;
32 import java.util.jar.*;
33 import org.openjdk.jigsaw.ModuleFileParser.Event;
34 import static org.openjdk.jigsaw.FileConstants.ModuleFile.*;
35 import static org.openjdk.jigsaw.FileConstants.ModuleFile.SectionType.*;
36 import static org.openjdk.jigsaw.ModuleFileParser.Event.*;
37
38 public final class ModuleFile {
39 /**
40 * Return the subdir of a section in an extracted module file.
41 */
42 public static String getSubdirOfSection(SectionType type) {
43 switch (type) {
44 case MODULE_INFO:
45 case SIGNATURE:
46 return ".";
47 case CLASSES:
48 case RESOURCES:
49 return "classes";
50 case NATIVE_LIBS:
51 return "lib";
52 case NATIVE_CMDS:
53 return "bin";
54 case CONFIG:
55 return "etc";
56 default:
57 throw new AssertionError(type);
58 }
59 }
60
61 /**
62 * Returns a ModuleFileParser instance.
63 *
64 * @param stream
65 * module file stream
66 *
67 * @return a module file parser
68 *
69 * @throws ModuleFileParserException
70 * If there is an error processing the underlying module file
71 */
72 public static ModuleFileParser newParser(InputStream stream) {
73 return new ModuleFileParserImpl(stream);
74 }
75
76 public final static class Reader implements Closeable {
77 private final ModuleFileParser parser;
78 private final ModuleFileHeader fileHeader;
79 private final byte[] moduleInfoBytes;
80 private final SignatureType moduleSignatureType;
81 private final byte[] moduleSignatureBytes ;
82 private final List<byte[]> calculatedHashes;
83
84 private File destination;
85 private boolean deflate;
86 private File natlibs;
87 private File natcmds;
88 private File configs;
89
90 public Reader(InputStream stream) throws IOException {
91 calculatedHashes = new ArrayList<>();
92 parser = ModuleFile.newParser(stream);
93 fileHeader = parser.fileHeader();
94 calculatedHashes.add(parser.getHash());
95 // Read the MODULE_INFO and the Signature section (if present),
96 // but does not write any files.
97 parser.next();
98 ByteArrayOutputStream baos = new ByteArrayOutputStream();
99 copyStream(parser.getRawStream(), baos);
100 moduleInfoBytes = baos.toByteArray();
101 assert moduleInfoBytes != null;
102
103 if (parser.next() != END_SECTION)
104 throw new ModuleFileParserException(
105 "Expected END_SECTION of module-info");
106 calculatedHashes.add(parser.getHash());
107
108 if (parser.hasNext()) {
109 Event event = parser.next();
110 if (event != END_FILE) { // more sections
111 SectionHeader header = parser.getSectionHeader();
112 if (header.type == SIGNATURE) {
113 SignatureSection sh = SignatureSection.read(
114 new DataInputStream(parser.getRawStream()));
115 moduleSignatureType = SignatureType.valueOf(sh.getSignatureType());
116 moduleSignatureBytes = sh.getSignature();
117 if (parser.next() != END_SECTION)
118 throw new ModuleFileParserException(
119 "Expected END_SECTION of signature");
120 if (parser.hasNext())
121 parser.next(); // position parser at next event
122 return;
123 }
124 }
125 }
126 // no signature section, or possibly other sections at all.
127 moduleSignatureBytes = null;
128 moduleSignatureType = null;
129 }
130
131 public void extractTo(File dst) throws IOException {
132 extractTo(dst, false);
133 }
134
135 public void extractTo(File dst, boolean deflate) throws IOException {
136 extractTo(dst, deflate, null, null, null);
137 }
138
139 public void extractTo(File dst, boolean deflate, File natlibs,
140 File natcmds, File configs)
141 throws IOException
142 {
143 this.deflate = deflate;
144 this.destination = dst != null ? dst.getCanonicalFile() : null;
145 this.natlibs = natlibs != null ? natlibs : new File(destination, "lib");
146 this.natcmds = natcmds != null ? natcmds : new File(destination, "bin");
147 this.configs = configs != null ? configs : new File(destination, "etc");
148
149 try {
150 Files.store(moduleInfoBytes, computeRealPath("info"));
151
152 Event event = parser.event();
153 if (event == END_FILE)
154 return;
155
156 if (event != START_SECTION)
157 throw new ModuleFileParserException(
158 "Expected START_SECTION, got : " + event);
159 // Module-Info and Signature, if present, have been consumed
160 do {
161 SectionHeader header = parser.getSectionHeader();
162 SectionType type = header.getType();
163 if (type.hasFiles()) {
164 while(parser.skipToNextStartSubSection()) {
165 readSubSection(type);
166 }
167 } else if (type == CLASSES) {
168 Iterator<Map.Entry<String,InputStream>> classes =
169 parser.getClasses();
170 while (classes.hasNext()) {
171 Map.Entry<String,InputStream> entry = classes.next();
172 try (OutputStream out = openOutputStream(type, entry.getKey())) {
173 copyStream(entry.getValue(), out);
174 }
175 }
176 // END_SECTION
177 parser.next();
178 } else {
179 throw new IllegalArgumentException("Unknown type: " + type);
180 }
181 byte[] sectionHash = parser.getHash();
182 calculatedHashes.add(sectionHash);
183 checkHashMatch(sectionHash, header.getHash());
184 } while (parser.skipToNextStartSection());
185
186 if (parser.event() != END_FILE)
187 throw new IOException("Expected END_FILE");
188 byte[] fileHeaderHash = fileHeader.getHashNoClone();
189 checkHashMatch(fileHeaderHash, parser.getHash());
190 calculatedHashes.add(fileHeaderHash);
191 } finally {
192 close();
193 }
194 }
195
196 public byte[] getModuleInfoBytes() {
197 return moduleInfoBytes.clone();
198 }
199
200 public byte[] getHash() {
201 return fileHeader.getHash();
202 }
203
204 public List<byte[]> getCalculatedHashes() {
205 return calculatedHashes;
206 }
207
208 public boolean hasSignature() {
209 return moduleSignatureBytes != null;
210 }
211
212 public SignatureType getSignatureType() {
213 return moduleSignatureType;
214 }
215
216 public byte[] getSignature() {
217 return moduleSignatureBytes == null ? null :
218 moduleSignatureBytes.clone();
219 }
220
221 /*package*/byte[] getSignatureNoClone() {
222 return moduleSignatureBytes;
223 }
224
225 public void close() throws IOException {
226 try {
227 try {
228 if (contentStream != null) {
229 contentStream.close();
230 contentStream = null;
231 }
232 } finally {
233 /*if (parser != null) {
234 parser.close();
235 parser = null;
236 }*/
237 }
238 } finally {
239 if (filesWriter != null) {
240 filesWriter.close();
241 filesWriter = null;
242 }
243 }
244 }
245
246 // subsections/files (resources, libs, cmds, configs)
247 public void readSubSection(SectionType type) throws IOException {
248 assert type == RESOURCES || type == NATIVE_LIBS ||
249 type == NATIVE_CMDS || type == CONFIG;
250
251 SubSectionFileHeader subHeader = parser.getSubSectionFileHeader();
252 String path = subHeader.getPath();
253 try (OutputStream sink = openOutputStream(type, path)) {
254 copyStream(parser.getContentStream(), sink);
255 }
256
257 // post processing for executable and files outside the module dir
258 postExtract(type, currentPath);
259 }
260
261 private JarOutputStream contentStream = null;
262
263 private JarOutputStream contentStream() throws IOException {
264 if (contentStream != null)
265 return contentStream;
266
267 return contentStream = new JarOutputStream(
268 new BufferedOutputStream(
269 new FileOutputStream(computeRealPath("classes"))));
270 }
271
272 private File currentPath = null;
273
274 private OutputStream openOutputStream(SectionType type, String path)
275 throws IOException
276 {
277 currentPath = null;
278 if (type == CLASSES || type == RESOURCES)
279 return Files.newOutputStream(contentStream(), deflate, path);
280 currentPath = computeRealPath(type, path);
281 File parent = currentPath.getParentFile();
282 if (!parent.exists())
283 Files.mkdirs(parent, currentPath.getName());
284 return new BufferedOutputStream(new FileOutputStream(currentPath));
285 }
286
287 private static void checkHashMatch(byte[] expected, byte[] computed)
288 throws IOException
289 {
290 if (!MessageDigest.isEqual(expected, computed))
291 throw new IOException("Expected hash "
292 + hashHexString(expected)
293 + " instead of "
294 + hashHexString(computed));
295 }
296
297 // Track files installed outside the module library. For later removal.
298 // files are relative to the modules directory.
299 private PrintWriter filesWriter;
300
301 private void trackFiles(File file)
302 throws IOException
303 {
304 if (file == null || file.toPath().startsWith(destination.toPath()))
305 return;
306
307 // Lazy construction, not all modules will need this.
308 if (filesWriter == null)
309 filesWriter = new PrintWriter(computeRealPath("files"), "UTF-8");
310
311 filesWriter.println(Files.convertSeparator(relativize(destination, file)));
312 filesWriter.flush();
313 }
314
315 List<IOException> remove() {
316 return ModuleFile.Reader.remove(destination);
317 }
318
319 // Removes a module, given its module install directory
320 static List<IOException> remove(File moduleDir) {
321 List<IOException> excs = new ArrayList<>();
328 Charset.forName("UTF-8"));
329 for (String fn : filenames) {
330 try {
331 Files.delete(new File(moduleDir,
332 Files.platformSeparator(fn)));
333 } catch (IOException x) {
334 excs.add(x);
335 }
336 }
337 } catch (IOException x) {
338 excs.add(x);
339 }
340 }
341
342 excs.addAll(Files.deleteTreeUnchecked(moduleDir.toPath()));
343 return excs;
344 }
345
346 // Returns the absolute path of the given section type.
347 private File getDirOfSection(SectionType type) {
348 if (type == NATIVE_LIBS)
349 return natlibs;
350 else if (type == NATIVE_CMDS)
351 return natcmds;
352 else if (type == CONFIG)
353 return configs;
354
355 // resolve sub dir section paths against the modules directory
356 return new File(destination, ModuleFile.getSubdirOfSection(type));
357 }
358
359 private File computeRealPath(String path) throws IOException {
360 return resolveAndNormalize(destination, path);
361 }
362
363 private File computeRealPath(SectionType type, String storedpath)
364 throws IOException
365 {
366 File sectionPath = getDirOfSection(type);
367 File realpath = new File(sectionPath,
368 Files.ensureNonAbsolute(Files.platformSeparator(storedpath)));
369
370 validatePath(sectionPath, realpath);
371
372 // Create the parent directories if necessary
373 File parent = realpath.getParentFile();
374 if (!parent.exists())
375 Files.mkdirs(parent, realpath.getName());
376
377 return realpath;
378 }
379
380 private static void markNativeCodeExecutable(SectionType type,
381 File file)
382 {
383 if (type == NATIVE_CMDS || (type == NATIVE_LIBS &&
384 System.getProperty("os.name").startsWith("Windows")))
385 file.setExecutable(true);
386 }
387
388 private void postExtract(SectionType type, File path)
389 throws IOException
390 {
391 markNativeCodeExecutable(type, path);
392 trackFiles(path);
393 }
394 }
395
396 private static void checkCompressor(SectionType type,
397 Compressor compressor) {
398
399 if ((MODULE_INFO == type && Compressor.NONE != compressor) ||
400 (CLASSES == type && Compressor.PACK200_GZIP != compressor))
401 throw new IllegalArgumentException(type
402 + " may not use compressor "
403 + compressor);
404 }
405
406 private static void checkSubsectionCount(SectionType type,
407 short subsections) {
408 if (!type.hasFiles() && subsections != 0)
409 throw new IllegalArgumentException(type
410 + " subsection count not 0: "
411 + subsections);
412 else if (type.hasFiles() && subsections == 0)
413 throw new IllegalArgumentException(type + " subsection count is 0");
414 }
415
416 private static void copyStream(InputStream in, OutputStream out)
417 throws IOException
418 {
419 byte[] buf = new byte[8192];
420 int read;
421 while ((read = in.read(buf)) > 0)
422 out.write(buf, 0, read);
423 }
424
425 private static void ensureNonNegativity(long size, String parameter) {
426 if (size < 0)
427 throw new IllegalArgumentException(parameter + "<0: " + size);
428 }
429
430 private static void ensureNonNull(Object reference, String parameter) {
431 if (null == reference)
432 throw new IllegalArgumentException(parameter + " == null");
433 }
434
435 private static void ensureMatch(int found, int expected, String field)
436 throws IOException
437 {
438 if (found != expected)
439 throw new IOException(field + " expected : "
440 + Integer.toHexString(expected) + " found: "
441 + Integer.toHexString(found));
442 }
443
444 private static void ensureShortNativePath(File path, String name)
535
536 return hashLength;
537 }
538
539 private static byte[] readHashBytes(DataInputStream in, short hashLength)
540 throws IOException
541 {
542 final byte[] hash = new byte[hashLength];
543 in.readFully(hash);
544
545 return hash;
546 }
547
548 private static byte[] readHash(DataInputStream in) throws IOException {
549 return readHashBytes(in, readHashLength(in));
550 }
551
552 private static byte[] readFileHash(DigestInputStream dis)
553 throws IOException
554 {
555 DataInputStream in = new DataInputStream(dis);
556
557 final short hashLength = readHashLength(in);
558
559 // Turn digest computation off before reading the file hash
560 dis.on(false);
561 byte[] hash = readHashBytes(in, hashLength);
562 // Turn digest computation on again afterwards.
563 dis.on(true);
564
565 return hash;
566 }
567
568 public final static class ModuleFileHeader {
569 public static final int LENGTH_WITHOUT_HASH = 30;
570 public static final int LENGTH =
571 LENGTH_WITHOUT_HASH + HashType.SHA256.length();
572
573 // Fields are specified as unsigned. Treat signed values as bugs.
574 private final int magic; // MAGIC
786 }
787
788 public void write(DataOutput out) throws IOException {
789 out.writeShort(SubSectionType.FILE.value());
790 out.writeInt(csize);
791 out.writeUTF(path);
792 }
793
794 public static SubSectionFileHeader read(DataInputStream in)
795 throws IOException
796 {
797 final short type = in.readShort();
798 ensureMatch(type, SubSectionType.FILE.value(),
799 "ModuleFile.SubSectionType.FILE");
800 final int csize = in.readInt();
801 final String path = in.readUTF();
802
803 return new SubSectionFileHeader(csize, path);
804 }
805 }
806
807 public final static class SignatureSection {
808 private final int signatureType; // One of FileConstants.ModuleFile.HashType
809 private final int signatureLength; // Length of signature
810 private final byte[] signature; // Signature bytes
811
812 public int getSignatureType() {
813 return signatureType;
814 }
815
816 public int getSignatureLength() {
817 return signatureLength;
818 }
819
820 public byte[] getSignature() {
821 return signature;
822 }
823
824 public SignatureSection(int signatureType, int signatureLength,
825 byte[] signature) {
826 ensureNonNegativity(signatureLength, "signatureLength");
827
828 this.signatureType = signatureType;
829 this.signatureLength = signatureLength;
830 this.signature = signature.clone();
831 }
832
833 public void write(DataOutput out) throws IOException {
834 out.writeShort(signatureType);
835 out.writeInt(signatureLength);
836 out.write(signature);
837 }
838
839 public static SignatureSection read(DataInputStream in)
840 throws IOException
841 {
842 short signatureType = in.readShort();
843 try {
844 SignatureType.valueOf(signatureType);
845 } catch (IllegalArgumentException x) {
846 throw new IOException("Invalid signature type: " + signatureType);
847 }
848 final int signatureLength = in.readInt();
849 ensureNonNegativity(signatureLength, "signatureLength");
850 final byte[] signature = new byte[signatureLength];
851 in.readFully(signature);
852 return new SignatureSection(signatureType, signatureLength,
853 signature);
854 }
855 }
856
857 private static void writeHash(DataOutput out, byte[] hash)
858 throws IOException
859 {
860 out.writeShort(hash.length);
861 out.write(hash);
862 }
863 }
|