1 /*
2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
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.io.File;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.lang.module.ModuleDescriptor;
32 import java.lang.module.ModuleDescriptor.Exports;
33 import java.lang.module.ModuleDescriptor.Opens;
34 import java.lang.module.ModuleDescriptor.Provides;
35 import java.lang.module.ModuleDescriptor.Requires;
36 import java.util.Collections;
37 import java.util.HashMap;
38 import java.util.HashSet;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Set;
42 import java.util.TreeMap;
43 import java.util.function.Function;
44 import java.util.stream.Collectors;
45 import java.util.zip.ZipEntry;
46 import java.util.zip.ZipFile;
47 
48 import static java.util.jar.JarFile.MANIFEST_NAME;
49 import static sun.tools.jar.Main.VERSIONS_DIR;
50 import static sun.tools.jar.Main.VERSIONS_DIR_LENGTH;
51 import static sun.tools.jar.Main.MODULE_INFO;
52 import static sun.tools.jar.Main.getMsg;
53 import static sun.tools.jar.Main.formatMsg;
54 import static sun.tools.jar.Main.formatMsg2;
55 import static sun.tools.jar.Main.toBinaryName;
56 
57 final class Validator {
58 
59     private final Map<String,FingerPrint> classes = new HashMap<>();
60     private final Main main;
61     private final ZipFile zf;
62     private boolean isValid = true;
63     private Set<String> concealedPkgs = Collections.emptySet();
64     private ModuleDescriptor md;
65     private String mdName;
66 
Validator(Main main, ZipFile zf)67     private Validator(Main main, ZipFile zf) {
68         this.main = main;
69         this.zf = zf;
70         checkModuleDescriptor(MODULE_INFO);
71     }
72 
validate(Main main, ZipFile zf)73     static boolean validate(Main main, ZipFile zf) throws IOException {
74         return new Validator(main, zf).validate();
75     }
76 
validate()77     private boolean validate() {
78         try {
79             zf.stream()
80               .filter(e -> e.getName().endsWith(".class"))
81               .map(this::getFingerPrint)
82               .filter(FingerPrint::isClass)    // skip any non-class entry
83               .collect(Collectors.groupingBy(
84                       FingerPrint::mrversion,
85                       TreeMap::new,
86                       Collectors.toMap(FingerPrint::className,
87                                        Function.identity(),
88                                        this::sameNameFingerPrint)))
89               .forEach((version, entries) -> {
90                       if (version == 0)
91                           validateBase(entries);
92                       else
93                           validateVersioned(entries);
94                   });
95         } catch (InvalidJarException e) {
96             errorAndInvalid(e.getMessage());
97         }
98         return isValid;
99     }
100 
101     static class InvalidJarException extends RuntimeException {
102         private static final long serialVersionUID = -3642329147299217726L;
InvalidJarException(String msg)103         InvalidJarException(String msg) {
104             super(msg);
105         }
106     }
107 
sameNameFingerPrint(FingerPrint fp1, FingerPrint fp2)108     private FingerPrint sameNameFingerPrint(FingerPrint fp1, FingerPrint fp2) {
109         checkClassName(fp1);
110         checkClassName(fp2);
111         // entries/classes with same name, return fp2 for now ?
112         return fp2;
113     }
114 
getFingerPrint(ZipEntry ze)115     private FingerPrint getFingerPrint(ZipEntry ze) {
116         // figure out the version and basename from the ZipEntry
117         String ename = ze.getName();
118         String bname = ename;
119         int version = 0;
120 
121         if (ename.startsWith(VERSIONS_DIR)) {
122             int n = ename.indexOf("/", VERSIONS_DIR_LENGTH);
123             if (n == -1) {
124                 throw new InvalidJarException(
125                     formatMsg("error.validator.version.notnumber", ename));
126             }
127             try {
128                 version = Integer.parseInt(ename, VERSIONS_DIR_LENGTH, n, 10);
129             } catch (NumberFormatException x) {
130                 throw new InvalidJarException(
131                     formatMsg("error.validator.version.notnumber", ename));
132             }
133             if (n == ename.length()) {
134                 throw new InvalidJarException(
135                     formatMsg("error.validator.entryname.tooshort", ename));
136             }
137             bname = ename.substring(n + 1);
138         }
139 
140         // return the cooresponding fingerprint entry
141         try (InputStream is = zf.getInputStream(ze)) {
142             return new FingerPrint(bname, ename, version, is.readAllBytes());
143         } catch (IOException x) {
144            throw new InvalidJarException(x.getMessage());
145         }
146     }
147 
148     /*
149      *  Validates (a) if there is any isolated nested class, and (b) if the
150      *  class name in class file (by asm) matches the entry's basename.
151      */
validateBase(Map<String, FingerPrint> fps)152     public void validateBase(Map<String, FingerPrint> fps) {
153         fps.values().forEach( fp -> {
154             if (!checkClassName(fp)) {
155                 return;
156             }
157             if (fp.isNestedClass()) {
158                 checkNestedClass(fp, fps);
159             }
160             classes.put(fp.className(), fp);
161         });
162     }
163 
validateVersioned(Map<String, FingerPrint> fps)164     public void validateVersioned(Map<String, FingerPrint> fps) {
165 
166         fps.values().forEach( fp -> {
167 
168             // validate the versioned module-info
169             if (MODULE_INFO.equals(fp.basename())) {
170                 checkModuleDescriptor(fp.entryName());
171                 return;
172             }
173             // process a versioned entry, look for previous entry with same name
174             FingerPrint matchFp = classes.get(fp.className());
175             if (matchFp == null) {
176                 // no match found
177                 if (fp.isNestedClass()) {
178                     checkNestedClass(fp, fps);
179                     return;
180                 }
181                 if (fp.isPublicClass()) {
182                     if (!isConcealed(fp.className())) {
183                         errorAndInvalid(formatMsg("error.validator.new.public.class",
184                                                   fp.entryName()));
185                         return;
186                      }
187                      // entry is a public class entry in a concealed package
188                      warn(formatMsg("warn.validator.concealed.public.class",
189                                    fp.entryName()));
190                 }
191                 classes.put(fp.className(), fp);
192                 return;
193             }
194 
195             // are the two classes/resources identical?
196             if (fp.isIdentical(matchFp)) {
197                 warn(formatMsg("warn.validator.identical.entry", fp.entryName()));
198                 return;    // it's okay, just takes up room
199             }
200 
201             // ok, not identical, check for compatible class version and api
202             if (fp.isNestedClass()) {
203                 checkNestedClass(fp, fps);
204                 return;    // fall through, need check nested public class??
205             }
206             if (!fp.isCompatibleVersion(matchFp)) {
207                 errorAndInvalid(formatMsg("error.validator.incompatible.class.version",
208                                           fp.entryName()));
209                 return;
210             }
211             if (!fp.isSameAPI(matchFp)) {
212                 errorAndInvalid(formatMsg("error.validator.different.api",
213                                           fp.entryName()));
214                 return;
215             }
216             if (!checkClassName(fp)) {
217                 return;
218             }
219             classes.put(fp.className(), fp);
220 
221             return;
222         });
223     }
224 
225     /*
226      * Checks whether or not the given versioned module descriptor's attributes
227      * are valid when compared against the root/base module descriptor.
228      *
229      * A versioned module descriptor must be identical to the root/base module
230      * descriptor, with two exceptions:
231      *  - A versioned descriptor can have different non-public `requires`
232      *    clauses of platform ( `java.*` and `jdk.*` ) modules, and
233      *  - A versioned descriptor can have different `uses` clauses, even of
234     *    service types defined outside of the platform modules.
235      */
checkModuleDescriptor(String miName)236     private void checkModuleDescriptor(String miName) {
237         ZipEntry ze = zf.getEntry(miName);
238         if (ze != null) {
239             try (InputStream jis = zf.getInputStream(ze)) {
240                 ModuleDescriptor md = ModuleDescriptor.read(jis);
241                 // Initialize the base md if it's not yet. A "base" md can be either the
242                 // root module-info.class or the first versioned module-info.class
243                 ModuleDescriptor base = this.md;
244 
245                 if (base == null) {
246                     concealedPkgs = new HashSet<>(md.packages());
247                     md.exports().stream().map(Exports::source).forEach(concealedPkgs::remove);
248                     md.opens().stream().map(Opens::source).forEach(concealedPkgs::remove);
249                     // must have the implementation class of the services it 'provides'.
250                     if (md.provides().stream().map(Provides::providers)
251                           .flatMap(List::stream)
252                           .filter(p -> zf.getEntry(toBinaryName(p)) == null)
253                           .peek(p -> error(formatMsg("error.missing.provider", p)))
254                           .count() != 0) {
255                         isValid = false;
256                         return;
257                     }
258                     this.md = md;
259                     this.mdName = miName;
260                     return;
261                 }
262 
263                 if (!base.name().equals(md.name())) {
264                     errorAndInvalid(getMsg("error.validator.info.name.notequal"));
265                 }
266                 if (!base.requires().equals(md.requires())) {
267                     Set<Requires> baseRequires = base.requires();
268                     for (Requires r : md.requires()) {
269                         if (baseRequires.contains(r))
270                             continue;
271                         if (r.modifiers().contains(Requires.Modifier.TRANSITIVE)) {
272                             errorAndInvalid(getMsg("error.validator.info.requires.transitive"));
273                         } else if (!isPlatformModule(r.name())) {
274                             errorAndInvalid(getMsg("error.validator.info.requires.added"));
275                         }
276                     }
277                     for (Requires r : baseRequires) {
278                         Set<Requires> mdRequires = md.requires();
279                         if (mdRequires.contains(r))
280                             continue;
281                         if (!isPlatformModule(r.name())) {
282                             errorAndInvalid(getMsg("error.validator.info.requires.dropped"));
283                         }
284                     }
285                 }
286                 if (!base.exports().equals(md.exports())) {
287                     errorAndInvalid(getMsg("error.validator.info.exports.notequal"));
288                 }
289                 if (!base.opens().equals(md.opens())) {
290                     errorAndInvalid(getMsg("error.validator.info.opens.notequal"));
291                 }
292                 if (!base.provides().equals(md.provides())) {
293                     errorAndInvalid(getMsg("error.validator.info.provides.notequal"));
294                 }
295                 if (!base.mainClass().equals(md.mainClass())) {
296                     errorAndInvalid(formatMsg("error.validator.info.manclass.notequal",
297                                               ze.getName()));
298                 }
299                 if (!base.version().equals(md.version())) {
300                     errorAndInvalid(formatMsg("error.validator.info.version.notequal",
301                                               ze.getName()));
302                 }
303             } catch (Exception x) {
304                 errorAndInvalid(x.getMessage() + " : " + miName);
305             }
306         }
307     }
308 
checkClassName(FingerPrint fp)309     private boolean checkClassName(FingerPrint fp) {
310         if (fp.className().equals(className(fp.basename()))) {
311             return true;
312         }
313         error(formatMsg2("error.validator.names.mismatch",
314                          fp.entryName(), fp.className().replace("/", ".")));
315         return isValid = false;
316     }
317 
checkNestedClass(FingerPrint fp, Map<String, FingerPrint> outerClasses)318     private boolean checkNestedClass(FingerPrint fp, Map<String, FingerPrint> outerClasses) {
319         if (outerClasses.containsKey(fp.outerClassName())) {
320             return true;
321         }
322         // outer class was not available
323 
324         error(formatMsg("error.validator.isolated.nested.class", fp.entryName()));
325         return isValid = false;
326     }
327 
isConcealed(String className)328     private boolean isConcealed(String className) {
329         if (concealedPkgs.isEmpty()) {
330             return false;
331         }
332         int idx = className.lastIndexOf('/');
333         String pkgName = idx != -1 ? className.substring(0, idx).replace('/', '.') : "";
334         return concealedPkgs.contains(pkgName);
335     }
336 
isPlatformModule(String name)337     private static boolean isPlatformModule(String name) {
338         return name.startsWith("java.") || name.startsWith("jdk.");
339     }
340 
className(String entryName)341     private static String className(String entryName) {
342         return entryName.endsWith(".class") ? entryName.substring(0, entryName.length() - 6) : null;
343     }
344 
error(String msg)345     private void error(String msg) {
346         main.error(msg);
347     }
348 
errorAndInvalid(String msg)349     private void errorAndInvalid(String msg) {
350         main.error(msg);
351         isValid = false;
352     }
353 
warn(String msg)354     private void warn(String msg) {
355         main.warn(msg);
356     }
357 }
358