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 67 private Validator(Main main, ZipFile zf) { 68 this.main = main; 69 this.zf = zf; 70 checkModuleDescriptor(MODULE_INFO); 71 } 72 73 static boolean validate(Main main, ZipFile zf) throws IOException { 74 return new Validator(main, zf).validate(); 75 } 76 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; 103 InvalidJarException(String msg) { 104 super(msg); 105 } 106 } 107 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 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 */ 152 public void validateBase(Map<String, FingerPrint> fps) { 153 fps.values().forEach( fp -> { 154 if (!checkClassName(fp)) { 155 isValid = false; 156 return; 157 } 158 if (fp.isNestedClass()) { 159 if (!checkNestedClass(fp, fps)) { 160 isValid = false; 161 } 162 } 163 classes.put(fp.className(), fp); 164 }); 165 } 166 167 public void validateVersioned(Map<String, FingerPrint> fps) { 168 169 fps.values().forEach( fp -> { 170 171 // validate the versioned module-info 172 if (MODULE_INFO.equals(fp.basename())) { 173 checkModuleDescriptor(fp.entryName()); 174 return; 175 } 176 // process a versioned entry, look for previous entry with same name 177 FingerPrint matchFp = classes.get(fp.className()); 178 if (matchFp == null) { 179 // no match found 180 if (fp.isNestedClass()) { 181 if (!checkNestedClass(fp, fps)) { 182 isValid = false; 183 } 184 return; 185 } 186 if (fp.isPublicClass()) { 187 if (!isConcealed(fp.className())) { 188 errorAndInvalid(formatMsg("error.validator.new.public.class", 189 fp.entryName())); 190 return; 191 } 192 // entry is a public class entry in a concealed package 193 warn(formatMsg("warn.validator.concealed.public.class", 194 fp.entryName())); 195 } 196 classes.put(fp.className(), fp); 197 return; 198 } 199 200 // are the two classes/resources identical? 201 if (fp.isIdentical(matchFp)) { 202 warn(formatMsg("warn.validator.identical.entry", fp.entryName())); 203 return; // it's okay, just takes up room 204 } 205 206 // ok, not identical, check for compatible class version and api 207 if (fp.isNestedClass()) { 208 if (!checkNestedClass(fp, fps)) { 209 isValid = false; 210 } 211 return; // fall through, need check nested public class?? 212 } 213 if (!fp.isCompatibleVersion(matchFp)) { 214 errorAndInvalid(formatMsg("error.validator.incompatible.class.version", 215 fp.entryName())); 216 return; 217 } 218 if (!fp.isSameAPI(matchFp)) { 219 errorAndInvalid(formatMsg("error.validator.different.api", 220 fp.entryName())); 221 return; 222 } 223 if (!checkClassName(fp)) { 224 isValid = false; 225 return; 226 } 227 classes.put(fp.className(), fp); 228 229 return; 230 }); 231 } 232 233 /* 234 * Checks whether or not the given versioned module descriptor's attributes 235 * are valid when compared against the root/base module descriptor. 236 * 237 * A versioned module descriptor must be identical to the root/base module 238 * descriptor, with two exceptions: 239 * - A versioned descriptor can have different non-public `requires` 240 * clauses of platform ( `java.*` and `jdk.*` ) modules, and 241 * - A versioned descriptor can have different `uses` clauses, even of 242 * service types defined outside of the platform modules. 243 */ 244 private void checkModuleDescriptor(String miName) { 245 ZipEntry ze = zf.getEntry(miName); 246 if (ze != null) { 247 try (InputStream jis = zf.getInputStream(ze)) { 248 ModuleDescriptor md = ModuleDescriptor.read(jis); 249 // Initialize the base md if it's not yet. A "base" md can be either the 250 // root module-info.class or the first versioned module-info.class 251 ModuleDescriptor base = this.md; 252 253 if (base == null) { 254 concealedPkgs = new HashSet<>(md.packages()); 255 md.exports().stream().map(Exports::source).forEach(concealedPkgs::remove); 256 md.opens().stream().map(Opens::source).forEach(concealedPkgs::remove); 257 // must have the implementation class of the services it 'provides'. 258 if (md.provides().stream().map(Provides::providers) 259 .flatMap(List::stream) 260 .filter(p -> zf.getEntry(toBinaryName(p)) == null) 261 .peek(p -> error(formatMsg("error.missing.provider", p))) 262 .count() != 0) { 263 isValid = false; 264 return; 265 } 266 this.md = md; 267 this.mdName = miName; 268 return; 269 } 270 271 if (!base.name().equals(md.name())) { 272 errorAndInvalid(getMsg("error.validator.info.name.notequal")); 273 } 274 if (!base.requires().equals(md.requires())) { 275 Set<Requires> baseRequires = base.requires(); 276 for (Requires r : md.requires()) { 277 if (baseRequires.contains(r)) 278 continue; 279 if (r.modifiers().contains(Requires.Modifier.TRANSITIVE)) { 280 errorAndInvalid(getMsg("error.validator.info.requires.transitive")); 281 } else if (!isPlatformModule(r.name())) { 282 errorAndInvalid(getMsg("error.validator.info.requires.added")); 283 } 284 } 285 for (Requires r : baseRequires) { 286 Set<Requires> mdRequires = md.requires(); 287 if (mdRequires.contains(r)) 288 continue; 289 if (!isPlatformModule(r.name())) { 290 errorAndInvalid(getMsg("error.validator.info.requires.dropped")); 291 } 292 } 293 } 294 if (!base.exports().equals(md.exports())) { 295 errorAndInvalid(getMsg("error.validator.info.exports.notequal")); 296 } 297 if (!base.opens().equals(md.opens())) { 298 errorAndInvalid(getMsg("error.validator.info.opens.notequal")); 299 } 300 if (!base.provides().equals(md.provides())) { 301 errorAndInvalid(getMsg("error.validator.info.provides.notequal")); 302 } 303 if (!base.mainClass().equals(md.mainClass())) { 304 errorAndInvalid(formatMsg("error.validator.info.manclass.notequal", 305 ze.getName())); 306 } 307 if (!base.version().equals(md.version())) { 308 errorAndInvalid(formatMsg("error.validator.info.version.notequal", 309 ze.getName())); 310 } 311 } catch (Exception x) { 312 errorAndInvalid(x.getMessage() + " : " + miName); 313 } 314 } 315 } 316 317 private boolean checkClassName(FingerPrint fp) { 318 if (fp.className().equals(className(fp.basename()))) { 319 return true; 320 } 321 error(formatMsg2("error.validator.names.mismatch", 322 fp.entryName(), fp.className().replace("/", "."))); 323 return false; 324 } 325 326 private boolean checkNestedClass(FingerPrint fp, Map<String, FingerPrint> outerClasses) { 327 if (outerClasses.containsKey(fp.outerClassName())) { 328 return true; 329 } 330 // outer class was not available 331 error(formatMsg("error.validator.isolated.nested.class", fp.entryName())); 332 return false; 333 } 334 335 private boolean isConcealed(String className) { 336 if (concealedPkgs.isEmpty()) { 337 return false; 338 } 339 int idx = className.lastIndexOf('/'); 340 String pkgName = idx != -1 ? className.substring(0, idx).replace('/', '.') : ""; 341 return concealedPkgs.contains(pkgName); 342 } 343 344 private static boolean isPlatformModule(String name) { 345 return name.startsWith("java.") || name.startsWith("jdk."); 346 } 347 348 private static String className(String entryName) { 349 return entryName.endsWith(".class") ? entryName.substring(0, entryName.length() - 6) : null; 350 } 351 352 private void error(String msg) { 353 main.error(msg); 354 } 355 356 private void errorAndInvalid(String msg) { 357 main.error(msg); 358 isValid = false; 359 } 360 361 private void warn(String msg) { 362 main.warn(msg); 363 } 364 }