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 HashType hashtype;
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;
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 hashtype = HashType.SHA256;
121 // Ensure that mark/reset is supported
122 if (stream.markSupported()) {
123 this.stream = stream;
124 } else {
125 this.stream =
126 new DataInputStream(new BufferedInputStream(stream));
127 }
128 }
129
130 private void checkHashMatch(byte[] expected, byte[] computed)
131 throws IOException
132 {
133 if (!MessageDigest.isEqual(expected, computed))
134 throw new IOException("Expected hash "
135 + hashHexString(expected)
136 + " instead of "
137 + hashHexString(computed));
138 }
139
140 private ModuleFileHeader fileHeader = null;
141 private MessageDigest fileDigest = null;
142 private MessageDigest sectionDigest = null;
143 private DataInputStream fileIn = null;
144 private byte[] moduleInfoBytes = null;
145 private Integer moduleSignatureType = null;
146 private byte[] moduleSignatureBytes = null;
147 private final int MAX_SECTION_HEADER_LENGTH = 128;
148 private List<byte[]> calculatedHashes = new ArrayList<>();
149 private boolean extract = true;
150
151 /*
152 * Reads the MODULE_INFO section and the Signature section, if present,
153 * but does not write any files.
154 */
155 public byte[] readStart() throws IOException {
156
157 try {
158 fileDigest = getHashInstance(hashtype);
159 sectionDigest = getHashInstance(hashtype);
160 DigestInputStream dis =
161 new DigestInputStream(stream, fileDigest);
162 fileHeader = ModuleFileHeader.read(dis);
163 // calculate module header hash
164 ByteArrayOutputStream baos = new ByteArrayOutputStream();
165 fileHeader.write(new DataOutputStream(baos));
166 sectionDigest.update(baos.toByteArray());
167 calculatedHashes.add(sectionDigest.digest());
168
169 fileIn = new DataInputStream(dis);
170 if (readSection(fileIn) != SectionType.MODULE_INFO)
171 throw new IOException("First module-file section"
172 + " is not MODULE_INFO");
173 assert moduleInfoBytes != null;
174
175 // Read the Signature Section, if present
176 readSignatureSection(fileIn, dis);
177
178 return moduleInfoBytes.clone();
179 } catch (IOException x) {
180 close();
181 throw x;
182 }
183 }
184
185 public void readRest() throws IOException {
186 extract = false;
187 readRest(null, false, null, null, null);
188 }
189
190 public void readRest(File dst, boolean deflate) throws IOException {
191 readRest(dst, deflate, null, null, null);
192 }
193
194 public void readRest(File dst, boolean deflate, File natlibs,
195 File natcmds, File configs)
196 throws IOException
197 {
198 this.deflate = deflate;
199 this.destination = dst != null ? dst.getCanonicalFile() : null;
200 this.natlibs = natlibs != null ? natlibs : new File(destination, "lib");
201 this.natcmds = natcmds != null ? natcmds : new File(destination, "bin");
202 this.configs = configs != null ? configs : new File(destination, "etc");
203 try {
204 if (extract)
205 Files.store(moduleInfoBytes, computeRealPath("info"));
206 // Module-Info and Signature, if present, have been consumed
207
208 // Read rest of file until all sections have been read
209 stream.mark(1);
210 while (-1 != stream.read()) {
211 stream.reset();
212 readSection(fileIn);
213 stream.mark(1);
214 }
215
216 close();
217 byte[] fileHeaderHash = fileHeader.getHashNoClone();
218 checkHashMatch(fileHeaderHash, fileDigest.digest());
219 calculatedHashes.add(fileHeaderHash);
220 } finally {
221 close();
222 }
223 }
224
225 public byte[] getHash() throws IOException {
226 if (null == fileHeader)
227 readStart();
228 return fileHeader.getHash();
229 }
230
239 }
240
241 public Integer getSignatureType() throws IOException {
242 if (null == fileHeader)
243 readStart();
244 return moduleSignatureType;
245 }
246
247 public byte[] getSignature() throws IOException {
248 if (null == fileHeader)
249 readStart();
250 return moduleSignatureBytes != null
251 ? moduleSignatureBytes.clone()
252 : null;
253 }
254
255 byte[] getSignatureNoClone() {
256 return moduleSignatureBytes;
257 }
258
259 private JarOutputStream contentStream = null;
260
261 private JarOutputStream contentStream() throws IOException {
262 if (contentStream == null) {
263 if (extract) {
264 FileOutputStream fos
265 = new FileOutputStream(computeRealPath("classes"));
266 contentStream
267 = new JarOutputStream(new BufferedOutputStream(fos));
268 } else {
269 contentStream = new JarOutputStream(new NullOutputStream());
270 }
271 }
272 return contentStream;
273 }
274
275 public void close() throws IOException {
276 try {
277 try {
278 if (contentStream != null) {
279 contentStream.close();
280 contentStream = null;
281 }
282 } finally {
283 if (fileIn != null) {
284 fileIn.close();
285 fileIn = null;
286 }
287 }
288 } finally {
289 if (filesWriter != null) {
290 filesWriter.close();
291 filesWriter = null;
292 }
293 }
294 }
295
296 public void readModule() throws IOException {
297 extract = false;
298 readStart();
299 readRest();
300 }
301
302 public void readModule(File dst) throws IOException {
303 readStart();
304 readRest(dst, false);
305 }
306
307 private void readSignatureSection(DataInputStream stream,
308 DigestInputStream dis)
309 throws IOException
310 {
311
312 // Turn off digest computation before reading Signature Section
313 dis.on(false);
314
315 // Mark the starting position
316 stream.mark(MAX_SECTION_HEADER_LENGTH);
317 if (stream.read() != -1) {
318 stream.reset();
319 SectionHeader header = SectionHeader.read(stream);
320 if (header != null &&
321 header.getType() == SectionType.SIGNATURE) {
322 readSectionContent(header, stream);
323 } else {
324 // Revert back to the starting position
325 stream.reset();
326 }
327 }
328
329 // Turn on digest computation again
330 dis.on(true);
331 }
332
333 private SectionType readSection(DataInputStream stream)
334 throws IOException
335 {
336 SectionHeader header = SectionHeader.read(stream);
337 readSectionContent(header, stream);
338 return header.getType();
339 }
340
341 private void readSectionContent(SectionHeader header,
342 DataInputStream stream)
343 throws IOException
344 {
345 SectionType type = header.getType();
346 Compressor compressor = header.getCompressor();
347 int csize = header.getCSize();
348 short subsections =
349 type.hasFiles() ? header.getSubsections() : 1;
350
351 CountingInputStream cs = new CountingInputStream(stream, csize);
352 sectionDigest.reset();
353 DigestInputStream dis = new DigestInputStream(cs, sectionDigest);
354 DataInputStream in = new DataInputStream(dis);
355
356 for (int subsection = 0; subsection < subsections; subsection++)
357 readFile(in, compressor, type, csize);
358
359 byte[] headerHash = header.getHashNoClone();
360 checkHashMatch(headerHash, sectionDigest.digest());
361 if (header.getType() != SectionType.SIGNATURE) {
362 calculatedHashes.add(headerHash);
363 }
364 }
365
366 public void readFile(DataInputStream in,
367 Compressor compressor,
368 SectionType type,
369 int csize)
370 throws IOException
371 {
372 switch (compressor) {
373 case NONE:
374 if (type == SectionType.MODULE_INFO) {
375 moduleInfoBytes = readModuleInfo(in, csize);
376
377 } else if (type == SectionType.SIGNATURE) {
378 // Examine the Signature header
379 moduleSignatureType = (int)in.readShort();
380 int length = in.readInt();
381 moduleSignatureBytes = readModuleSignature(in, csize - 6);
382 if (length != moduleSignatureBytes.length) {
383 throw new IOException("Invalid Signature length");
384 }
385 } else {
386 readUncompressedFile(in, type, csize);
387 }
388 break;
389 case GZIP:
390 readGZIPCompressedFile(in, type);
391 break;
392 case PACK200_GZIP:
393 readClasses(
394 new DataInputStream(new CountingInputStream(in, csize)));
395 break;
396 default:
397 throw new IOException("Unsupported Compressor for files: " +
398 compressor);
399 }
400 }
401
402 public void readClasses(DataInputStream in) throws IOException {
403 unpack200gzip(in);
404 }
405
406 private File currentPath = null;
407
408 private OutputStream openOutputStream(SectionType type,
409 String path)
410 throws IOException
411 {
412 if (!extract)
413 return new NullOutputStream();
414 currentPath = null;
415 assert type != SectionType.CLASSES;
416 if (type == SectionType.RESOURCES)
417 return Files.newOutputStream(contentStream(), path);
418 currentPath = computeRealPath(type, path);
419 File parent = currentPath.getParentFile();
420 if (!parent.exists())
421 Files.mkdirs(parent, currentPath.getName());
422 return new BufferedOutputStream(new FileOutputStream(currentPath));
423 }
424
425 private static class NullOutputStream extends OutputStream {
426 @Override
427 public void write(int b) throws IOException {}
428 @Override
429 public void write(byte[] b) throws IOException {}
430 @Override
431 public void write(byte[] b, int off, int len) throws IOException {}
432 }
433
434 public void readGZIPCompressedFile(DataInputStream in,
435 SectionType type)
436 throws IOException
437 {
438 SubSectionFileHeader header = SubSectionFileHeader.read(in);
439 int csize = header.getCSize();
440
441 // Splice off the compressed file from input stream
442 ByteArrayOutputStream baos = new ByteArrayOutputStream();
443 copyStream(new CountingInputStream(in, csize), baos, csize);
444
445 byte[] compressedfile = baos.toByteArray();
446 ByteArrayInputStream bain
447 = new ByteArrayInputStream(compressedfile);
448 try (GZIPInputStream gin = new GZIPInputStream(bain);
449 OutputStream out = openOutputStream(type, header.getPath())) {
450 copyStream(gin, out);
451 }
452
453 if (extract)
454 postExtract(type, currentPath);
455 }
456
457 public void readUncompressedFile(DataInputStream in,
458 SectionType type,
459 int csize)
460 throws IOException
461 {
462 assert type != SectionType.MODULE_INFO;
463 SubSectionFileHeader header = SubSectionFileHeader.read(in);
464 csize = header.getCSize();
465 try (OutputStream out = openOutputStream(type, header.getPath())) {
466 CountingInputStream cin = new CountingInputStream(in, csize);
467 byte[] buf = new byte[8192];
468 int n;
469 while ((n = cin.read(buf)) >= 0)
470 out.write(buf, 0, n);
471 }
472 if (extract) {
473 postExtract(type, currentPath);
474 }
475 }
476
477 public byte[] readModuleInfo(DataInputStream in, int csize)
478 throws IOException
479 {
480 CountingInputStream cin = new CountingInputStream(in, csize);
481 ByteArrayOutputStream out = new ByteArrayOutputStream();
482 byte[] buf = new byte[8192];
483 int n;
484 while ((n = cin.read(buf)) >= 0)
485 out.write(buf, 0, n);
486 return out.toByteArray();
487 }
488
489 public byte[] readModuleSignature(DataInputStream in, int csize)
490 throws IOException
491 {
492 return readModuleInfo(in, csize); // signature has the same format
493 }
494
495 // Track files installed outside the module library. For later removal.
496 // files are relative to the modules directory.
497 private PrintWriter filesWriter;
498
499 private void trackFiles(SectionType type, File file)
500 throws IOException
501 {
502 if (file == null || file.toPath().startsWith(destination.toPath()))
503 return;
504
505 // Lazy construction, not all modules will need this.
506 if (filesWriter == null)
507 filesWriter = new PrintWriter(computeRealPath("files"), "UTF-8");
508
509 filesWriter.println(Files.convertSeparator(relativize(destination, file)));
510 filesWriter.flush();
511 }
512
513 List<IOException> remove() {
514 return ModuleFile.Reader.remove(destination);
515 }
516
517 // Removes a module, given its module install directory
518 static List<IOException> remove(File moduleDir) {
519 List<IOException> excs = new ArrayList<>();
526 Charset.forName("UTF-8"));
527 for (String fn : filenames) {
528 try {
529 Files.delete(new File(moduleDir,
530 Files.platformSeparator(fn)));
531 } catch (IOException x) {
532 excs.add(x);
533 }
534 }
535 } catch (IOException x) {
536 excs.add(x);
537 }
538 }
539
540 excs.addAll(Files.deleteTreeUnchecked(moduleDir.toPath()));
541 return excs;
542 }
543
544 // Returns the absolute path of the given section type.
545 private File getDirOfSection(SectionType type) {
546 if (type == SectionType.NATIVE_LIBS)
547 return natlibs;
548 else if (type == SectionType.NATIVE_CMDS)
549 return natcmds;
550 else if (type == SectionType.CONFIG)
551 return configs;
552
553 // resolve sub dir section paths against the modules directory
554 return new File(destination, ModuleFile.getSubdirOfSection(type));
555 }
556
557 private File computeRealPath(String path) throws IOException {
558 return resolveAndNormalize(destination, path);
559 }
560
561 private File computeRealPath(SectionType type, String storedpath)
562 throws IOException
563 {
564 File sectionPath = getDirOfSection(type);
565 File realpath = new File(sectionPath,
566 Files.ensureNonAbsolute(Files.platformSeparator(storedpath)));
567
568 validatePath(sectionPath, realpath);
569
570 // Create the parent directories if necessary
571 File parent = realpath.getParentFile();
572 if (!parent.exists())
573 Files.mkdirs(parent, realpath.getName());
574
575 return realpath;
576 }
577
578 private static void markNativeCodeExecutable(SectionType type,
579 File file)
580 {
581 if (type == SectionType.NATIVE_CMDS
582 || (type == SectionType.NATIVE_LIBS
583 && System.getProperty("os.name").startsWith("Windows")))
584 {
585 file.setExecutable(true);
586 }
587 }
588
589 private void postExtract(SectionType type, File path)
590 throws IOException
591 {
592 markNativeCodeExecutable(type, path);
593 trackFiles(type, path);
594 }
595
596 private void unpack200gzip(DataInputStream in) throws IOException {
597 GZIPInputStream gis = new GZIPInputStream(in) {
598 public void close() throws IOException {}
599 };
600 Pack200.Unpacker unpacker = Pack200.newUnpacker();
601 if (deflate) {
602 Map<String,String> p = unpacker.properties();
603 p.put(Pack200.Unpacker.DEFLATE_HINT, Pack200.Unpacker.TRUE);
604 }
605 unpacker.unpack(gis, contentStream());
606 }
607
608 }
609
610 private static void checkCompressor(SectionType type,
611 Compressor compressor) {
612
613 if ((SectionType.MODULE_INFO == type &&
614 Compressor.NONE != compressor)
615 || (SectionType.CLASSES == type &&
616 Compressor.PACK200_GZIP != compressor))
617 throw new IllegalArgumentException(type
618 + " may not use compressor "
619 + compressor);
620 }
621
622 private static void checkSubsectionCount(SectionType type,
623 short subsections) {
624 if (!type.hasFiles() && subsections != 0)
625 throw new IllegalArgumentException(type
626 + " subsection count not 0: "
627 + subsections);
628 else if (type.hasFiles() && subsections == 0)
629 throw new IllegalArgumentException(type + " subsection count is 0");
630 }
631
632 private static void copyStream(InputStream in, DataOutput out)
633 throws IOException
634 {
635
636 byte[] buffer = new byte[1024 * 8];
637 for (int b_read = in.read(buffer);
638 -1 != b_read;
639 b_read = in.read(buffer))
640 out.write(buffer, 0, b_read);
641 }
642
643 private static void copyStream(InputStream in, OutputStream out)
644 throws IOException
645 {
646 copyStream(in, (DataOutput) new DataOutputStream(out));
647 }
648
649 private static void copyStream(InputStream in, DataOutput out,
650 int count)
651 throws IOException
652 {
653 byte[] buffer = new byte[1024 * 8];
654
655 while(count > 0) {
656 int b_read = in.read(buffer, 0, Math.min(count, buffer.length));
657 if (-1 == b_read)
658 return;
659 out.write(buffer, 0, b_read);
660 count-=b_read;
661 }
662 }
663
664 private static void copyStream(InputStream in, OutputStream out,
665 int count)
666 throws IOException
667 {
668 copyStream(in, (DataOutput) new DataOutputStream(out), count);
669 }
670
671 private static void ensureNonNegativity(long size, String parameter) {
672 if (size < 0)
673 throw new IllegalArgumentException(parameter + "<0: " + size);
674 }
675
676 private static void ensureNonNull(Object reference, String parameter) {
677 if (null == reference)
678 throw new IllegalArgumentException(parameter + " == null");
679 }
680
681 private static void ensureMatch(int found, int expected, String field)
682 throws IOException
683 {
684 if (found != expected)
685 throw new IOException(field + " expected : "
686 + Integer.toHexString(expected) + " found: "
687 + Integer.toHexString(found));
688 }
689
690 private static void ensureShortNativePath(File path, String name)
769 throws IOException
770 {
771 if (!child.toPath().startsWith(parent.toPath()) )
772 throw new IOException("Bogus relative path: " + child);
773 if (child.exists()) {
774 // conflict, for now just fail
775 throw new IOException("File " + child + " already exists");
776 }
777 }
778
779 private static short readHashLength(DataInputStream in) throws IOException {
780 final short hashLength = in.readShort();
781 ensureNonNegativity(hashLength, "hashLength");
782
783 return hashLength;
784 }
785
786 private static byte[] readHashBytes(DataInputStream in, short hashLength)
787 throws IOException
788 {
789
790 final byte[] hash = new byte[hashLength];
791 in.readFully(hash);
792
793 return hash;
794 }
795
796 private static byte[] readHash(DataInputStream in) throws IOException {
797 return readHashBytes(in, readHashLength(in));
798 }
799
800 private static byte[] readFileHash(DigestInputStream dis)
801 throws IOException
802 {
803
804 DataInputStream in = new DataInputStream(dis);
805
806 final short hashLength = readHashLength(in);
807
808 // Turn digest computation off before reading the file hash
809 dis.on(false);
810 byte[] hash = readHashBytes(in, hashLength);
811 // Turn digest computation on again afterwards.
812 dis.on(true);
813
814 return hash;
815 }
816
817 public final static class ModuleFileHeader {
818 public static final int LENGTH_WITHOUT_HASH = 30;
819 public static final int LENGTH =
820 LENGTH_WITHOUT_HASH + HashType.SHA256.length();
821
822 // Fields are specified as unsigned. Treat signed values as bugs.
823 private final int magic; // MAGIC
1037 }
1038
1039 public void write(DataOutput out) throws IOException {
1040 out.writeShort(SubSectionType.FILE.value());
1041 out.writeInt(csize);
1042 out.writeUTF(path);
1043 }
1044
1045 public static SubSectionFileHeader read(DataInputStream in)
1046 throws IOException
1047 {
1048 final short type = in.readShort();
1049 ensureMatch(type, SubSectionType.FILE.value(),
1050 "ModuleFile.SubSectionType.FILE");
1051 final int csize = in.readInt();
1052 final String path = in.readUTF();
1053
1054 return new SubSectionFileHeader(csize, path);
1055 }
1056 }
1057
1058 private static void writeHash(DataOutput out, byte[] hash)
1059 throws IOException
1060 {
1061 out.writeShort(hash.length);
1062 out.write(hash);
1063 }
1064 }
|
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 import static org.openjdk.jigsaw.FileConstants.ModuleFile.SectionType.*;
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 public final static class Reader implements Closeable {
62
63 private DataInputStream stream;
64 private File destination;
65 private boolean deflate;
66 private HashType hashtype;
67 private File natlibs;
68 private File natcmds;
69 private File configs;
70
71 private static class CountingInputStream extends FilterInputStream {
72 private int count;
73 public CountingInputStream(InputStream stream, int count) {
74 super(stream);
75 this.count = count;
76 }
77
78 public int available() throws IOException {
79 return count;
80 }
81
82 public boolean markSupported() {
83 return false;
84 }
85
86 public int read() throws IOException {
87 if (count == 0)
88 return -1;
89 int read = super.read();
90 if (-1 != read)
91 count--;
92 return read;
98 len = Math.min(len, count);
99 int read = super.read(b, off, len);
100 if (-1 != read)
101 count-=read;
102 return read;
103 }
104
105 public void reset() throws IOException {
106 throw new IOException("Can't reset this stream");
107 }
108
109 public long skip(long n) throws IOException {
110 if (count == 0)
111 return -1;
112 n = Math.min(n, count);
113 long skipped = super.skip(n);
114 if (n > 0)
115 count-=skipped;
116 return skipped;
117 }
118
119 public void close() throws IOException {
120 // Do nothing, CountingInputStream is used to wrap (sub)section
121 // content. We never want to close the underlying stream.
122 }
123 }
124
125 public Reader(DataInputStream stream) {
126 hashtype = HashType.SHA256;
127 // Ensure that mark/reset is supported
128 if (stream.markSupported()) {
129 this.stream = stream;
130 } else {
131 this.stream =
132 new DataInputStream(new BufferedInputStream(stream));
133 }
134 }
135
136 private ModuleFileHeader fileHeader = null;
137 private MessageDigest fileDigest = null;
138 private MessageDigest sectionDigest = null;
139 private DataInputStream fileIn = null;
140 private byte[] moduleInfoBytes = null;
141 private Integer moduleSignatureType = null;
142 private byte[] moduleSignatureBytes = null;
143 private final int MAX_SECTION_HEADER_LENGTH = 128;
144 private List<byte[]> calculatedHashes = new ArrayList<>();
145 private boolean extract = true;
146 private List<String> contents; // list of the module-file contents
147
148 /*
149 * Reads the MODULE_INFO section and the Signature section, if present,
150 * but does not write any files.
151 */
152 public byte[] readStart() throws IOException {
153
154 try {
155 fileDigest = getHashInstance(hashtype);
156 sectionDigest = getHashInstance(hashtype);
157 DigestInputStream dis =
158 new DigestInputStream(stream, fileDigest);
159 fileHeader = ModuleFileHeader.read(dis);
160 // calculate module header hash
161 ByteArrayOutputStream baos = new ByteArrayOutputStream();
162 fileHeader.write(new DataOutputStream(baos));
163 sectionDigest.update(baos.toByteArray());
164 calculatedHashes.add(sectionDigest.digest());
165
166 fileIn = new DataInputStream(dis);
167 if (readSection(fileIn) != MODULE_INFO)
168 throw new IOException("First module-file section"
169 + " is not MODULE_INFO");
170 assert moduleInfoBytes != null;
171
172 // Read the Signature Section, if present
173 readSignatureSection(fileIn, dis);
174
175 return moduleInfoBytes.clone();
176 } catch (IOException x) {
177 close();
178 throw x;
179 }
180 }
181
182 public void readRest() throws IOException {
183 extract = false;
184 readRest(null, false, null, null, null);
185 }
186
187 public void readRest(File dst, boolean deflate) throws IOException {
188 readRest(dst, deflate, null, null, null);
189 }
190
191 public void readRest(File dst, boolean deflate, File natlibs,
192 File natcmds, File configs)
193 throws IOException
194 {
195 this.deflate = deflate;
196 this.destination = dst != null ? dst.getCanonicalFile() : null;
197 this.natlibs = natlibs != null ? natlibs : new File(destination, "lib");
198 this.natcmds = natcmds != null ? natcmds : new File(destination, "bin");
199 this.configs = configs != null ? configs : new File(destination, "etc");
200 contents = new ArrayList<>();
201
202 try {
203 if (extract)
204 Files.store(moduleInfoBytes, computeRealPath("info"));
205 contents.add("module-info.class");
206 // Module-Info and Signature, if present, have been consumed
207
208 // Read rest of file until all sections have been read
209 stream.mark(1);
210 while (stream.read() != -1) {
211 stream.reset();
212 readSection(fileIn);
213 stream.mark(1);
214 }
215
216 close();
217 byte[] fileHeaderHash = fileHeader.getHashNoClone();
218 checkHashMatch(fileHeaderHash, fileDigest.digest());
219 calculatedHashes.add(fileHeaderHash);
220 } finally {
221 close();
222 }
223 }
224
225 public byte[] getHash() throws IOException {
226 if (null == fileHeader)
227 readStart();
228 return fileHeader.getHash();
229 }
230
239 }
240
241 public Integer getSignatureType() throws IOException {
242 if (null == fileHeader)
243 readStart();
244 return moduleSignatureType;
245 }
246
247 public byte[] getSignature() throws IOException {
248 if (null == fileHeader)
249 readStart();
250 return moduleSignatureBytes != null
251 ? moduleSignatureBytes.clone()
252 : null;
253 }
254
255 byte[] getSignatureNoClone() {
256 return moduleSignatureBytes;
257 }
258
259 public List<String> getContents() throws IOException {
260 if (contents == null)
261 readModule();
262 Collections.sort(contents);
263 return contents;
264 }
265
266 public void close() throws IOException {
267 try {
268 try {
269 if (contentStream != null) {
270 contentStream.close();
271 contentStream = null;
272 }
273 } finally {
274 if (fileIn != null) {
275 fileIn.close();
276 fileIn = null;
277 }
278 }
279 } finally {
280 if (filesWriter != null) {
281 filesWriter.close();
282 filesWriter = null;
283 }
284 }
285 }
286
287 public void readModule() throws IOException {
288 extract = false;
289 readStart();
290 readRest();
291 }
292
293 public void readModule(File dst) throws IOException {
294 readStart();
295 readRest(dst, false);
296 }
297
298 private void readSignatureSection(DataInputStream stream,
299 DigestInputStream dis)
300 throws IOException
301 {
302 // Turn off digest computation before reading Signature Section
303 dis.on(false);
304
305 // Mark the starting position
306 stream.mark(MAX_SECTION_HEADER_LENGTH);
307 if (stream.read() != -1) {
308 stream.reset();
309 SectionHeader header = SectionHeader.read(stream);
310 if (header != null && header.getType() == SIGNATURE)
311 readSectionContent(header, stream);
312 else
313 stream.reset(); // No signature, reset back to start
314 }
315
316 // Turn on digest computation again
317 dis.on(true);
318 }
319
320 private SectionType readSection(DataInputStream stream)
321 throws IOException
322 {
323 SectionHeader header = SectionHeader.read(stream);
324 readSectionContent(header, stream);
325 return header.getType();
326 }
327
328 private void readSectionContent(SectionHeader header,
329 DataInputStream stream)
330 throws IOException
331 {
332 SectionType type = header.getType();
333 Compressor compressor = header.getCompressor();
334 int csize = header.getCSize();
335
336 CountingInputStream cs = new CountingInputStream(stream, csize);
337 sectionDigest.reset();
338 DigestInputStream dis = new DigestInputStream(cs, sectionDigest);
339 DataInputStream in = new DataInputStream(dis);
340
341 if (type.hasFiles()) {
342 short subsections = header.getSubsections();
343 for (int subsection = 0; subsection < subsections; subsection++)
344 readSubSection(in, compressor, type);
345 } else if (type == CLASSES) {
346 readClassesContent(in, compressor);
347 } else {
348 readSectionBytes(in, compressor, type);
349 }
350
351 // ## appropriate exception ??
352 if (cs.read() != -1)
353 throw new IllegalArgumentException("All section content not read");
354
355 byte[] headerHash = header.getHashNoClone();
356 checkHashMatch(headerHash, sectionDigest.digest());
357 if (header.getType() != SIGNATURE) {
358 calculatedHashes.add(headerHash);
359 }
360 }
361
362 // module-info OR module signature
363 public void readSectionBytes(DataInputStream in, Compressor compressor,
364 SectionType type)
365 throws IOException
366 {
367 Decompressor decompressor = Decompressor.newInstance(in, compressor);
368 ByteArrayOutputStream baos = new ByteArrayOutputStream();
369 decompressor.extractTo(baos);
370
371 if (type == MODULE_INFO)
372 moduleInfoBytes = baos.toByteArray();
373 else if (type == SIGNATURE) {
374 SignatureSection sh = SignatureSection.read(new DataInputStream(
375 new ByteArrayInputStream(baos.toByteArray())));
376 moduleSignatureType = sh.getSignatureType();
377 moduleSignatureBytes = sh.getSignature();
378 } else
379 throw new IllegalArgumentException(
380 "Unsupported raw bytes section type: " + type);
381 }
382
383 public void readClassesContent(DataInputStream in, Compressor compressor)
384 throws IOException
385 {
386 ClassesDecompressor decompressor =
387 ClassesDecompressor.newInstance(in, compressor, deflate);
388 decompressor.extractTo(contentStream());
389 }
390
391 // subsections/files (resources, libs, cmds, configs)
392 public void readSubSection(DataInputStream in, Compressor compressor,
393 SectionType type)
394 throws IOException
395 {
396 assert type == RESOURCES || type == NATIVE_LIBS ||
397 type == NATIVE_CMDS || type == CONFIG;
398
399 SubSectionFileHeader header = SubSectionFileHeader.read(in);
400 CountingInputStream cs = new CountingInputStream(in, header.getCSize());
401 Decompressor decompressor = Decompressor.newInstance(cs, compressor);
402 String path = header.getPath();
403 try (OutputStream sink = openOutputStream(type, path)) {
404 decompressor.extractTo(sink);
405 }
406
407 String prefix = type == RESOURCES ? "" :
408 getSubdirOfSection(type) + File.separator;
409 contents.add(prefix + path);
410 // post processing for executable and files outside the module dir
411 if (extract)
412 postExtract(type, currentPath);
413 }
414
415 static class Decompressor {
416 protected InputStream source;
417 protected Decompressor() { }
418 protected Decompressor(InputStream source) {
419 // no decompression
420 this.source = source;
421 }
422
423 void extractTo(OutputStream sink) throws IOException {
424 copyStream(source, sink);
425 }
426
427 static Decompressor newInstance(InputStream source,
428 Compressor compressor)
429 throws IOException
430 {
431 switch (compressor) {
432 case NONE:
433 return new Decompressor(source);
434 case GZIP:
435 return new GZIPDecompressor(source);
436 default:
437 throw new IllegalArgumentException(
438 "Unsupported compressor type: " + compressor);
439 }
440 }
441 }
442
443 static class GZIPDecompressor extends Decompressor {
444 GZIPDecompressor(InputStream source) throws IOException {
445 this.source = new GZIPInputStream(source) {
446 public void close() throws IOException {}
447 };
448 }
449 }
450
451 static abstract class ClassesDecompressor {
452 protected InputStream source;
453
454 abstract void extractTo(JarOutputStream sink) throws IOException;
455
456 static ClassesDecompressor newInstance(InputStream source,
457 Compressor compressor,
458 boolean deflate)
459 throws IOException
460 {
461 switch (compressor) {
462 case PACK200_GZIP:
463 return new Pack200GZIPDecompressor(source, deflate);
464 default:
465 throw new IllegalArgumentException(
466 "Unsupported compressor type: " + compressor);
467 }
468 }
469 }
470
471 static class Pack200GZIPDecompressor extends ClassesDecompressor {
472 private Pack200.Unpacker unpacker;
473
474 Pack200GZIPDecompressor(InputStream source, boolean deflate)
475 throws IOException
476 {
477 this.source = new GZIPInputStream(source) {
478 public void close() throws IOException {}
479 };
480 unpacker = Pack200.newUnpacker();
481 if (deflate) {
482 Map<String,String> p = unpacker.properties();
483 p.put(Pack200.Unpacker.DEFLATE_HINT, Pack200.Unpacker.TRUE);
484 }
485 }
486
487 void extractTo(JarOutputStream sink) throws IOException {
488 unpacker.unpack(source, sink);
489 }
490 }
491
492 private TrackingJarOutputStream contentStream = null;
493
494 // Used to retrieved archive contents
495 private static class TrackingJarOutputStream extends JarOutputStream {
496 private final List<String> contents;
497 TrackingJarOutputStream(OutputStream out, List<String> contents)
498 throws IOException
499 {
500 super(out);
501 this.contents = contents;
502 }
503
504 public void putNextEntry(ZipEntry ze) throws IOException {
505 super.putNextEntry(ze);
506 contents.add(ze.getName());
507 }
508 }
509
510 private JarOutputStream contentStream() throws IOException {
511 if (contentStream != null)
512 return contentStream;
513
514 OutputStream sink;
515 if (extract)
516 sink = new BufferedOutputStream(
517 new FileOutputStream(computeRealPath("classes")));
518 else
519 sink = new NullOutputStream();
520
521 return contentStream = new TrackingJarOutputStream(sink, contents);
522 }
523
524 private File currentPath = null;
525
526 private OutputStream openOutputStream(SectionType type, String path)
527 throws IOException
528 {
529 if (!extract)
530 return new NullOutputStream();
531
532 currentPath = null;
533 assert type != CLASSES;
534 if (type == RESOURCES)
535 return Files.newOutputStream(contentStream(), deflate, path);
536 currentPath = computeRealPath(type, path);
537 File parent = currentPath.getParentFile();
538 if (!parent.exists())
539 Files.mkdirs(parent, currentPath.getName());
540 return new BufferedOutputStream(new FileOutputStream(currentPath));
541 }
542
543 private static class NullOutputStream extends OutputStream {
544 @Override
545 public void write(int b) throws IOException {}
546 @Override
547 public void write(byte[] b) throws IOException {}
548 @Override
549 public void write(byte[] b, int off, int len) throws IOException {}
550 }
551
552 private static void checkHashMatch(byte[] expected, byte[] computed)
553 throws IOException
554 {
555 if (!MessageDigest.isEqual(expected, computed))
556 throw new IOException("Expected hash "
557 + hashHexString(expected)
558 + " instead of "
559 + hashHexString(computed));
560 }
561
562 // Track files installed outside the module library. For later removal.
563 // files are relative to the modules directory.
564 private PrintWriter filesWriter;
565
566 private void trackFiles(File file)
567 throws IOException
568 {
569 if (file == null || file.toPath().startsWith(destination.toPath()))
570 return;
571
572 // Lazy construction, not all modules will need this.
573 if (filesWriter == null)
574 filesWriter = new PrintWriter(computeRealPath("files"), "UTF-8");
575
576 filesWriter.println(Files.convertSeparator(relativize(destination, file)));
577 filesWriter.flush();
578 }
579
580 List<IOException> remove() {
581 return ModuleFile.Reader.remove(destination);
582 }
583
584 // Removes a module, given its module install directory
585 static List<IOException> remove(File moduleDir) {
586 List<IOException> excs = new ArrayList<>();
593 Charset.forName("UTF-8"));
594 for (String fn : filenames) {
595 try {
596 Files.delete(new File(moduleDir,
597 Files.platformSeparator(fn)));
598 } catch (IOException x) {
599 excs.add(x);
600 }
601 }
602 } catch (IOException x) {
603 excs.add(x);
604 }
605 }
606
607 excs.addAll(Files.deleteTreeUnchecked(moduleDir.toPath()));
608 return excs;
609 }
610
611 // Returns the absolute path of the given section type.
612 private File getDirOfSection(SectionType type) {
613 if (type == NATIVE_LIBS)
614 return natlibs;
615 else if (type == NATIVE_CMDS)
616 return natcmds;
617 else if (type == CONFIG)
618 return configs;
619
620 // resolve sub dir section paths against the modules directory
621 return new File(destination, ModuleFile.getSubdirOfSection(type));
622 }
623
624 private File computeRealPath(String path) throws IOException {
625 return resolveAndNormalize(destination, path);
626 }
627
628 private File computeRealPath(SectionType type, String storedpath)
629 throws IOException
630 {
631 File sectionPath = getDirOfSection(type);
632 File realpath = new File(sectionPath,
633 Files.ensureNonAbsolute(Files.platformSeparator(storedpath)));
634
635 validatePath(sectionPath, realpath);
636
637 // Create the parent directories if necessary
638 File parent = realpath.getParentFile();
639 if (!parent.exists())
640 Files.mkdirs(parent, realpath.getName());
641
642 return realpath;
643 }
644
645 private static void markNativeCodeExecutable(SectionType type,
646 File file)
647 {
648 if (type == NATIVE_CMDS || (type == NATIVE_LIBS &&
649 System.getProperty("os.name").startsWith("Windows")))
650 file.setExecutable(true);
651 }
652
653 private void postExtract(SectionType type, File path)
654 throws IOException
655 {
656 markNativeCodeExecutable(type, path);
657 trackFiles(path);
658 }
659 }
660
661 private static void checkCompressor(SectionType type,
662 Compressor compressor) {
663
664 if ((MODULE_INFO == type && Compressor.NONE != compressor) ||
665 (CLASSES == type && Compressor.PACK200_GZIP != compressor))
666 throw new IllegalArgumentException(type
667 + " may not use compressor "
668 + compressor);
669 }
670
671 private static void checkSubsectionCount(SectionType type,
672 short subsections) {
673 if (!type.hasFiles() && subsections != 0)
674 throw new IllegalArgumentException(type
675 + " subsection count not 0: "
676 + subsections);
677 else if (type.hasFiles() && subsections == 0)
678 throw new IllegalArgumentException(type + " subsection count is 0");
679 }
680
681 private static void copyStream(InputStream source, DataOutput sink)
682 throws IOException
683 {
684 byte[] buf = new byte[8192];
685 int b_read = 0;
686 while((b_read = source.read(buf)) > 0)
687 sink.write(buf, 0, b_read);
688 }
689
690 private static void copyStream(InputStream source, OutputStream sink)
691 throws IOException
692 {
693 copyStream(source, (DataOutput) new DataOutputStream(sink));
694 }
695
696 private static void ensureNonNegativity(long size, String parameter) {
697 if (size < 0)
698 throw new IllegalArgumentException(parameter + "<0: " + size);
699 }
700
701 private static void ensureNonNull(Object reference, String parameter) {
702 if (null == reference)
703 throw new IllegalArgumentException(parameter + " == null");
704 }
705
706 private static void ensureMatch(int found, int expected, String field)
707 throws IOException
708 {
709 if (found != expected)
710 throw new IOException(field + " expected : "
711 + Integer.toHexString(expected) + " found: "
712 + Integer.toHexString(found));
713 }
714
715 private static void ensureShortNativePath(File path, String name)
794 throws IOException
795 {
796 if (!child.toPath().startsWith(parent.toPath()) )
797 throw new IOException("Bogus relative path: " + child);
798 if (child.exists()) {
799 // conflict, for now just fail
800 throw new IOException("File " + child + " already exists");
801 }
802 }
803
804 private static short readHashLength(DataInputStream in) throws IOException {
805 final short hashLength = in.readShort();
806 ensureNonNegativity(hashLength, "hashLength");
807
808 return hashLength;
809 }
810
811 private static byte[] readHashBytes(DataInputStream in, short hashLength)
812 throws IOException
813 {
814 final byte[] hash = new byte[hashLength];
815 in.readFully(hash);
816
817 return hash;
818 }
819
820 private static byte[] readHash(DataInputStream in) throws IOException {
821 return readHashBytes(in, readHashLength(in));
822 }
823
824 private static byte[] readFileHash(DigestInputStream dis)
825 throws IOException
826 {
827 DataInputStream in = new DataInputStream(dis);
828
829 final short hashLength = readHashLength(in);
830
831 // Turn digest computation off before reading the file hash
832 dis.on(false);
833 byte[] hash = readHashBytes(in, hashLength);
834 // Turn digest computation on again afterwards.
835 dis.on(true);
836
837 return hash;
838 }
839
840 public final static class ModuleFileHeader {
841 public static final int LENGTH_WITHOUT_HASH = 30;
842 public static final int LENGTH =
843 LENGTH_WITHOUT_HASH + HashType.SHA256.length();
844
845 // Fields are specified as unsigned. Treat signed values as bugs.
846 private final int magic; // MAGIC
1060 }
1061
1062 public void write(DataOutput out) throws IOException {
1063 out.writeShort(SubSectionType.FILE.value());
1064 out.writeInt(csize);
1065 out.writeUTF(path);
1066 }
1067
1068 public static SubSectionFileHeader read(DataInputStream in)
1069 throws IOException
1070 {
1071 final short type = in.readShort();
1072 ensureMatch(type, SubSectionType.FILE.value(),
1073 "ModuleFile.SubSectionType.FILE");
1074 final int csize = in.readInt();
1075 final String path = in.readUTF();
1076
1077 return new SubSectionFileHeader(csize, path);
1078 }
1079 }
1080
1081 public final static class SignatureSection {
1082 private final int signatureType; // One of FileConstants.ModuleFile.HashType
1083 private final int signatureLength; // Length of signature
1084 private final byte[] signature; // Signature bytes
1085
1086 public int getSignatureType() {
1087 return signatureType;
1088 }
1089
1090 public int getSignatureLength() {
1091 return signatureLength;
1092 }
1093
1094 public byte[] getSignature() {
1095 return signature;
1096 }
1097
1098 public SignatureSection(int signatureType, int signatureLength,
1099 byte[] signature) {
1100 ensureNonNegativity(signatureLength, "signatureLength");
1101
1102 this.signatureType = signatureType;
1103 this.signatureLength = signatureLength;
1104 this.signature = signature.clone();
1105 }
1106
1107 public void write(DataOutput out) throws IOException {
1108 out.writeShort(signatureType);
1109 out.writeInt(signatureLength);
1110 out.write(signature);
1111 }
1112
1113 public static SignatureSection read(DataInputStream in)
1114 throws IOException
1115 {
1116 final short signatureType = in.readShort();
1117 ensureMatch(signatureType, SignatureType.PKCS7.value(), "SignatureType.PKCS7");
1118 final int signatureLength = in.readInt();
1119 ensureNonNegativity(signatureLength, "signatureLength");
1120 final byte[] signature = new byte[signatureLength];
1121 in.readFully(signature);
1122 return new SignatureSection(signatureType, signatureLength,
1123 signature);
1124 }
1125 }
1126
1127 private static void writeHash(DataOutput out, byte[] hash)
1128 throws IOException
1129 {
1130 out.writeShort(hash.length);
1131 out.write(hash);
1132 }
1133 }
|