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