12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
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 sun.tools.jar;
27
28 import java.lang.module.ModuleId;
29 import java.io.*;
30 import java.nio.file.Path;
31 import java.nio.file.Files;
32 import java.util.*;
33 import java.util.zip.*;
34 import java.util.jar.*;
35 import java.util.jar.Manifest;
36 import java.text.MessageFormat;
37 import sun.misc.JarIndex;
38 import static sun.misc.JarIndex.INDEX_NAME;
39 import static java.util.jar.JarFile.MANIFEST_NAME;
40 import static java.util.jar.JarFile.MODULEINFO_NAME;
41 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
42
43 /**
44 * This class implements a simple utility for creating files in the JAR
45 * (Java Archive) file format. The JAR format is based on the ZIP file
46 * format, with optional meta-information stored in a MANIFEST entry.
47 */
48 public
49 class Main {
50 String program;
51 PrintStream out, err;
589 if (e.getMethod() == ZipEntry.STORED) {
590 e2.setSize(e.getSize());
591 e2.setCrc(e.getCrc());
592 }
593 zos.putNextEntry(e2);
594 }
595
596 /**
597 * Updates an existing jar file.
598 */
599 boolean update(InputStream in, OutputStream out,
600 InputStream newManifest,
601 JarIndex jarIndex) throws IOException
602 {
603 ModuleInfo minfo = moduleid != null
604 ? new ModuleInfo(moduleid) : null;
605 try (ZipInputStream zis = new ZipInputStream(in);
606 ZipOutputStream zos = new JarOutputStream(out)) {
607 ZipEntry e = null;
608 Manifest mf = null;
609
610 if (entryMap.containsKey(MODULEINFO_NAME) && moduleid != null) {
611 error(getMsg("error.bad.moduleid"));
612 return false;
613 }
614
615 if (jarIndex != null) {
616 addIndex(jarIndex, zos);
617 }
618
619 // put the old entries first, replace if necessary
620 while ((e = zis.getNextEntry()) != null) {
621 String name = e.getName();
622
623 boolean isManifestEntry = equalsIgnoreCase(name, MANIFEST_NAME);
624
625 if (jarIndex != null && equalsIgnoreCase(name, INDEX_NAME)) {
626 continue;
627 }
628
629 if (isManifestEntry) {
630 mf = new Manifest(zis);
631 if (Mflag) {
632 continue;
633 }
634
635 if (newManifest != null) {
636 // Don't read from the newManifest InputStream, as we
637 // might need it below, and we can't re-read the same data
638 // twice.
639 try (FileInputStream fis = new FileInputStream(mname)) {
640 if (isAmbiguousMainClass(new Manifest(fis))) {
641 return false;
642 }
643 }
644 mf.read(newManifest);
645 }
646
647 if (ename != null) {
648 addMainClass(mf, ename);
649 }
650
651 // retain the manifest in the same location as
652 // the existing manifest entry. JarInputStream
653 // only creates a Manifest if it's the first entry;
654 // otherwise, it treats it as an ordinary ZipEntry.
655 if (newManifest != null || ename != null) {
656 updateManifest(mf, zos);
657 } else {
658 writeManifest(mf, zos, e.getTime());
659 }
660 continue;
661 }
662
663 if (!entryMap.containsKey(name)) { // copy the old stuff
664 if (!name.equals(MODULEINFO_NAME) || moduleid == null) {
665 // add exports in the generated module-info.java
666 copyZipEntry(e, zos);
667 copy(e, zis, zos, minfo); // copy the content
668 zos.closeEntry();
669 }
670 } else { // replace with the new files
671 File f = entryMap.get(name);
672 addFile(zos, f, minfo);
673 entryMap.remove(name);
674 entries.remove(f);
675 }
676 }
677
678 // add the remaining new files
679 for (File f : entries) {
680 addFile(zos, f, minfo);
681 }
682
683 if (mf == null) {
684 // META-INF/MANIFEST.MF not exist
685 if (newManifest != null) {
686 mf = new Manifest(newManifest);
687 if (isAmbiguousMainClass(mf))
688 return false;
689 } else if (ename != null) {
690 mf = new Manifest();
691 }
692 }
693
694 if (minfo != null) {
695 // -I is specified
696 minfo.setMainClass(getMainClass(mf));
697 addModuleRequires(minfo, getClassPath(mf));
698 writeModuleInfo(minfo, zos, System.currentTimeMillis());
699 if (vflag) {
700 output(getMsg("out.update.moduleinfo"));
701 }
702 }
703 return true;
704 }
705 }
706
707 private void addIndex(JarIndex index, ZipOutputStream zos)
708 throws IOException
709 {
710 ZipEntry e = new ZipEntry(INDEX_NAME);
711 e.setTime(System.currentTimeMillis());
712 if (flag0) {
713 CRC32OutputStream os = new CRC32OutputStream();
714 index.write(os);
715 os.updateEntry(e);
716 }
717 zos.putNextEntry(e);
731 }
732 zos.putNextEntry(e);
733 m.write(zos);
734 zos.closeEntry();
735 }
736
737 private void updateManifest(Manifest m, ZipOutputStream zos)
738 throws IOException
739 {
740 addVersion(m);
741 addCreatedBy(m);
742 if (ename != null) {
743 addMainClass(m, ename);
744 }
745 writeManifest(m, zos, System.currentTimeMillis());
746 if (vflag) {
747 output(getMsg("out.update.manifest"));
748 }
749 }
750
751 private void writeModuleInfo(ModuleInfo minfo, ZipOutputStream zos, long ts)
752 throws IOException
753 {
754 ZipEntry e = new ZipEntry(MODULEINFO_NAME);
755 e.setTime(ts);
756 if (flag0) {
757 CRC32OutputStream cos = new CRC32OutputStream();
758 minfo.write(cos);
759 cos.updateEntry(e);
760 }
761 zos.putNextEntry(e);
762 minfo.write(zos);
763 zos.closeEntry();
764 }
765
766 private String entryName(String name) {
767 name = name.replace(File.separatorChar, '/');
768 String matchPath = "";
769 for (String path : paths) {
770 if (name.startsWith(path)
921 * Copies all bytes from the input stream to the output stream.
922 * Does not close or flush either stream. Also, add exports
923 * to the given ModuleInfo.
924 */
925 void copy(ZipEntry e, InputStream from, OutputStream to,
926 ModuleInfo minfo) throws IOException {
927 String path = e.getName();
928 if (minfo == null || !path.endsWith(".class")) {
929 copy(from, to);
930 } else {
931 // avoid reading the input stream twice
932 // the ByteStreamHelper saves the bytes in a buffer
933 // for writing to the output stream and also loaded
934 // to determine if it's a public class and get its classname.
935 helper.copyFrom(from);
936 helper.copyTo(to);
937 helper.addExports(minfo, e);
938 }
939 }
940
941 private ByteStreamHelper helper = new ByteStreamHelper();
942 class ByteStreamHelper extends ByteArrayOutputStream {
943 void copyFrom(InputStream from) throws IOException {
944 reset();
945
946 int n;
947 while ((n = from.read(copyBuf)) != -1) {
948 this.write(copyBuf, 0, n);
949 }
950 }
951 void copyTo(OutputStream to) throws IOException {
952 to.write(buf);
953 }
954
955 void addExports(ModuleInfo minfo, ZipEntry e) throws IOException {
956 minfo.addExports(new ByteArrayInputStream(buf), e.getSize(), e.getName());
957 }
958 }
959 /**
960 * Copies all bytes from the input file to the output stream.
961 * Does not close or flush the output stream.
962 *
963 * @param from the input file to read from
964 * @param to the output stream to write to
965 * @throws IOException if an I/O error occurs
966 */
967 private void copy(File from, OutputStream to) throws IOException {
968 InputStream in = new FileInputStream(from);
969 try {
970 copy(in, to);
971 } finally {
972 in.close();
973 }
974 }
975
976 /**
977 * Copies all bytes from the input stream to the output file.
978 * Does not close the input stream.
979 *
980 * @param from the input stream to read from
981 * @param to the output file to write to
982 * @throws IOException if an I/O error occurs
983 */
984 private void copy(InputStream from, File to) throws IOException {
985 OutputStream out = new FileOutputStream(to);
986 try {
987 copy(from, out);
988 } finally {
989 out.close();
990 }
991 }
992
993 /**
994 * Computes the crc32 of a File. This is necessary when the
995 * ZipOutputStream is in STORED mode.
996 */
997 private void crc32File(ZipEntry e, File f) throws IOException {
998 CRC32OutputStream os = new CRC32OutputStream();
999 copy(f, os);
1000 if (os.n != f.length()) {
1001 throw new JarException(formatMsg(
1002 "error.incorrect.length", f.getPath()));
1003 }
1004 os.updateEntry(e);
1005 }
1006
1007 void replaceFSC(String files[]) {
1008 if (files != null) {
1009 for (String file : files) {
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
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 sun.tools.jar;
27
28 import java.lang.module.ModuleId;
29 import java.io.*;
30 import java.nio.file.Path;
31 import java.nio.file.Files;
32 import java.nio.charset.Charset;
33 import java.util.*;
34 import java.util.zip.*;
35 import java.util.jar.*;
36 import java.util.jar.Manifest;
37 import java.text.MessageFormat;
38 import sun.misc.JarIndex;
39 import static sun.misc.JarIndex.INDEX_NAME;
40 import static java.util.jar.JarFile.MANIFEST_NAME;
41 import static java.util.jar.JarFile.MODULEINFO_NAME;
42 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
43
44 /**
45 * This class implements a simple utility for creating files in the JAR
46 * (Java Archive) file format. The JAR format is based on the ZIP file
47 * format, with optional meta-information stored in a MANIFEST entry.
48 */
49 public
50 class Main {
51 String program;
52 PrintStream out, err;
590 if (e.getMethod() == ZipEntry.STORED) {
591 e2.setSize(e.getSize());
592 e2.setCrc(e.getCrc());
593 }
594 zos.putNextEntry(e2);
595 }
596
597 /**
598 * Updates an existing jar file.
599 */
600 boolean update(InputStream in, OutputStream out,
601 InputStream newManifest,
602 JarIndex jarIndex) throws IOException
603 {
604 ModuleInfo minfo = moduleid != null
605 ? new ModuleInfo(moduleid) : null;
606 try (ZipInputStream zis = new ZipInputStream(in);
607 ZipOutputStream zos = new JarOutputStream(out)) {
608 ZipEntry e = null;
609 Manifest mf = null;
610 Map<String,Set<String>> providers = new HashMap<>();
611
612 if (entryMap.containsKey(MODULEINFO_NAME) && moduleid != null) {
613 error(getMsg("error.bad.moduleid"));
614 return false;
615 }
616
617 if (jarIndex != null) {
618 addIndex(jarIndex, zos);
619 }
620
621 // put the old entries first, replace if necessary
622 while ((e = zis.getNextEntry()) != null) {
623 String name = e.getName();
624
625 boolean isManifestEntry = equalsIgnoreCase(name, MANIFEST_NAME);
626
627 String service = null;
628 if (moduleid != null && !e.isDirectory()
629 && name.startsWith("META-INF/services/")) {
630 int last = name.lastIndexOf("/");
631 service = name.substring(last + 1);
632 }
633
634 if (jarIndex != null && equalsIgnoreCase(name, INDEX_NAME)) {
635 continue;
636 }
637
638 if (isManifestEntry) {
639 mf = new Manifest(zis);
640 if (Mflag) {
641 continue;
642 }
643
644 if (newManifest != null) {
645 // Don't read from the newManifest InputStream, as we
646 // might need it below, and we can't re-read the same data
647 // twice.
648 try (FileInputStream fis = new FileInputStream(mname)) {
649 if (isAmbiguousMainClass(new Manifest(fis))) {
650 return false;
651 }
652 }
653 mf.read(newManifest);
654 }
655
656 if (ename != null) {
657 addMainClass(mf, ename);
658 }
659
660 // retain the manifest in the same location as
661 // the existing manifest entry. JarInputStream
662 // only creates a Manifest if it's the first entry;
663 // otherwise, it treats it as an ordinary ZipEntry.
664 if (newManifest != null || ename != null) {
665 updateManifest(mf, zos);
666 } else {
667 writeManifest(mf, zos, e.getTime());
668 }
669 continue;
670 }
671
672 if (!entryMap.containsKey(name)) { // copy the old stuff
673 if (!name.equals(MODULEINFO_NAME) || moduleid == null) {
674 copyZipEntry(e, zos);
675
676 // when generating a module-info file and this entry is
677 // a service configuration file then parse it to get the
678 // names of the service implementations.
679 if (service != null) {
680 byte[] bytes = readAllBytes(zis);
681 providers.put(service, parseServicesEntry(bytes));
682 zos.write(bytes); // copy the content
683 } else {
684 copy(e, zis, zos, minfo); // copy the content
685 }
686 zos.closeEntry();
687 }
688 } else { // replace with the new files
689 File f = entryMap.get(name);
690 addFile(zos, f, minfo);
691 entryMap.remove(name);
692 entries.remove(f);
693 }
694 }
695
696 // add the remaining new files
697 for (File f : entries) {
698 addFile(zos, f, minfo);
699 }
700
701 if (mf == null) {
702 // META-INF/MANIFEST.MF not exist
703 if (newManifest != null) {
704 mf = new Manifest(newManifest);
705 if (isAmbiguousMainClass(mf))
706 return false;
707 } else if (ename != null) {
708 mf = new Manifest();
709 }
710 }
711
712 if (minfo != null) {
713 // -I is specified
714 minfo.setMainClass(getMainClass(mf));
715 addModuleRequires(minfo, getClassPath(mf));
716
717 // service providers
718 for (Map.Entry<String,Set<String>> entry: providers.entrySet()) {
719 String service = entry.getKey();
720 for (String impl: entry.getValue()) {
721 minfo.addProvidesService(service, impl);
722 }
723 }
724
725 writeModuleInfo(minfo, zos, System.currentTimeMillis());
726 if (vflag) {
727 output(getMsg("out.update.moduleinfo"));
728 }
729 }
730 return true;
731 }
732 }
733
734 private void addIndex(JarIndex index, ZipOutputStream zos)
735 throws IOException
736 {
737 ZipEntry e = new ZipEntry(INDEX_NAME);
738 e.setTime(System.currentTimeMillis());
739 if (flag0) {
740 CRC32OutputStream os = new CRC32OutputStream();
741 index.write(os);
742 os.updateEntry(e);
743 }
744 zos.putNextEntry(e);
758 }
759 zos.putNextEntry(e);
760 m.write(zos);
761 zos.closeEntry();
762 }
763
764 private void updateManifest(Manifest m, ZipOutputStream zos)
765 throws IOException
766 {
767 addVersion(m);
768 addCreatedBy(m);
769 if (ename != null) {
770 addMainClass(m, ename);
771 }
772 writeManifest(m, zos, System.currentTimeMillis());
773 if (vflag) {
774 output(getMsg("out.update.manifest"));
775 }
776 }
777
778 /**
779 * Parse the given byte array as the raw bytes of a services configuration
780 * file, returning the names of the entries.
781 */
782 private Set<String> parseServicesEntry(byte[] bytes) throws IOException {
783 ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
784 BufferedReader reader =
785 new BufferedReader(new InputStreamReader(bis, Charset.forName("UTF-8")));
786 Set<String> result = new HashSet<>();
787 String s;
788 while ((s = reader.readLine()) != null) {
789 s = s.trim();
790 if (s.length() > 0 && !s.startsWith("#"))
791 result.add(s);
792 }
793 return result;
794 }
795
796 private void writeModuleInfo(ModuleInfo minfo, ZipOutputStream zos, long ts)
797 throws IOException
798 {
799 ZipEntry e = new ZipEntry(MODULEINFO_NAME);
800 e.setTime(ts);
801 if (flag0) {
802 CRC32OutputStream cos = new CRC32OutputStream();
803 minfo.write(cos);
804 cos.updateEntry(e);
805 }
806 zos.putNextEntry(e);
807 minfo.write(zos);
808 zos.closeEntry();
809 }
810
811 private String entryName(String name) {
812 name = name.replace(File.separatorChar, '/');
813 String matchPath = "";
814 for (String path : paths) {
815 if (name.startsWith(path)
966 * Copies all bytes from the input stream to the output stream.
967 * Does not close or flush either stream. Also, add exports
968 * to the given ModuleInfo.
969 */
970 void copy(ZipEntry e, InputStream from, OutputStream to,
971 ModuleInfo minfo) throws IOException {
972 String path = e.getName();
973 if (minfo == null || !path.endsWith(".class")) {
974 copy(from, to);
975 } else {
976 // avoid reading the input stream twice
977 // the ByteStreamHelper saves the bytes in a buffer
978 // for writing to the output stream and also loaded
979 // to determine if it's a public class and get its classname.
980 helper.copyFrom(from);
981 helper.copyTo(to);
982 helper.addExports(minfo, e);
983 }
984 }
985
986 /**
987 * Read all bytes from the input stream, returning them in a byte array.
988 */
989 private byte[] readAllBytes(InputStream from) throws IOException {
990 int capacity = 8192;
991 byte[] buf = new byte[capacity];
992 int nread = 0;
993 int rem = buf.length;
994 int n;
995 // read to EOF which may read more or less than initialSize (eg: file
996 // is truncated while we are reading)
997 while ((n = from.read(buf, nread, rem)) > 0) {
998 nread += n;
999 rem -= n;
1000 assert rem >= 0;
1001 if (rem == 0) {
1002 // need larger buffer
1003 int newCapacity = capacity << 1;
1004 if (newCapacity < 0) {
1005 if (capacity == Integer.MAX_VALUE)
1006 throw new OutOfMemoryError("Required array size too large");
1007 newCapacity = Integer.MAX_VALUE;
1008 }
1009 rem = newCapacity - capacity;
1010 buf = Arrays.copyOf(buf, newCapacity);
1011 capacity = newCapacity;
1012 }
1013 }
1014 return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
1015 }
1016
1017 private ByteStreamHelper helper = new ByteStreamHelper();
1018 class ByteStreamHelper extends ByteArrayOutputStream {
1019 void copyFrom(InputStream from) throws IOException {
1020 reset();
1021
1022 int n;
1023 while ((n = from.read(copyBuf)) != -1) {
1024 this.write(copyBuf, 0, n);
1025 }
1026 }
1027 void copyTo(OutputStream to) throws IOException {
1028 to.write(buf);
1029 }
1030
1031 void addExports(ModuleInfo minfo, ZipEntry e) throws IOException {
1032 minfo.addExports(new ByteArrayInputStream(buf), e.getSize(), e.getName());
1033 }
1034 }
1035 /**
1036 * Copies all bytes from the input file to the output stream.
1037 * Does not close or flush the output stream.
1038 *
1039 * @param from the input file to read from
1040 * @param to the output stream to write to
1041 * @throws IOException if an I/O error occurs
1042 */
1043 private void copy(File from, OutputStream to) throws IOException {
1044 try (InputStream in = new FileInputStream(from)) {
1045 copy(in, to);
1046 }
1047 }
1048
1049 /**
1050 * Copies all bytes from the input stream to the output file.
1051 * Does not close the input stream.
1052 *
1053 * @param from the input stream to read from
1054 * @param to the output file to write to
1055 * @throws IOException if an I/O error occurs
1056 */
1057 private void copy(InputStream from, File to) throws IOException {
1058 try (OutputStream out = new FileOutputStream(to)) {
1059 copy(from, out);
1060 }
1061 }
1062
1063 /**
1064 * Computes the crc32 of a File. This is necessary when the
1065 * ZipOutputStream is in STORED mode.
1066 */
1067 private void crc32File(ZipEntry e, File f) throws IOException {
1068 CRC32OutputStream os = new CRC32OutputStream();
1069 copy(f, os);
1070 if (os.n != f.length()) {
1071 throw new JarException(formatMsg(
1072 "error.incorrect.length", f.getPath()));
1073 }
1074 os.updateEntry(e);
1075 }
1076
1077 void replaceFSC(String files[]) {
1078 if (files != null) {
1079 for (String file : files) {
|