1 /* 2 * Copyright (c) 2016, 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.IOException; 29 import java.io.InputStream; 30 import java.util.HashMap; 31 import java.util.Map; 32 import java.util.function.Consumer; 33 import java.util.jar.JarEntry; 34 import java.util.jar.JarFile; 35 36 final class Validator implements Consumer<JarEntry> { 37 private final static boolean DEBUG = Boolean.getBoolean("jar.debug"); 38 private final Map<String,FingerPrint> fps = new HashMap<>(); 39 private final int vdlen = Main.VERSIONS_DIR.length(); 40 private final Main main; 41 private final JarFile jf; 42 private int oldVersion = -1; 43 private String currentTopLevelName; 44 private boolean isValid = true; 45 46 Validator(Main main, JarFile jf) { 47 this.main = main; 48 this.jf = jf; 49 } 50 51 boolean isValid() { 52 return isValid; 53 } 54 55 /* 56 * Validator has state and assumes entries provided to accept are ordered 57 * from base entries first and then through the versioned entries in 58 * ascending version order. Also, to find isolated nested classes, 59 * classes must be ordered so that the top level class is before the associated 60 * nested class(es). 61 */ 62 public void accept(JarEntry je) { 63 String entryName = je.getName(); 64 65 // directories are always accepted 66 if (entryName.endsWith("/")) { 67 debug("%s is a directory", entryName); 68 return; 69 } 70 71 // figure out the version and basename from the JarEntry 72 int version; 73 String basename; 74 if (entryName.startsWith(Main.VERSIONS_DIR)) { 75 int n = entryName.indexOf("/", vdlen); 76 if (n == -1) { 77 main.error(Main.formatMsg("error.validator.version.notnumber", entryName)); 78 isValid = false; 79 return; 80 } 81 String v = entryName.substring(vdlen, n); 82 try { 83 version = Integer.parseInt(v); 84 } catch (NumberFormatException x) { 85 main.error(Main.formatMsg("error.validator.version.notnumber", entryName)); 86 isValid = false; 87 return; 88 } 89 if (n == entryName.length()) { 90 main.error(Main.formatMsg("error.validator.entryname.tooshort", entryName)); 91 isValid = false; 92 return; 93 } 94 basename = entryName.substring(n + 1); 95 } else { 96 version = 0; 97 basename = entryName; 98 } 99 debug("\n===================\nversion %d %s", version, entryName); 100 101 if (oldVersion != version) { 102 oldVersion = version; 103 currentTopLevelName = null; 104 } 105 106 // analyze the entry, keeping key attributes 107 FingerPrint fp; 108 try (InputStream is = jf.getInputStream(je)) { 109 fp = new FingerPrint(basename, is.readAllBytes()); 110 } catch (IOException x) { 111 main.error(x.getMessage()); 112 isValid = false; 113 return; 114 } 115 String internalName = fp.name(); 116 117 // process a base entry paying attention to nested classes 118 if (version == 0) { 119 debug("base entry found"); 120 if (fp.isNestedClass()) { 121 debug("nested class found"); 122 if (fp.topLevelName().equals(currentTopLevelName)) { 123 fps.put(internalName, fp); 124 return; 125 } 126 main.error(Main.formatMsg("error.validator.isolated.nested.class", entryName)); 127 isValid = false; 128 return; 129 } 130 // top level class or resource entry 131 if (fp.isClass()) { 132 currentTopLevelName = fp.topLevelName(); 133 if (!checkInternalName(entryName, basename, internalName)) { 134 isValid = false; 135 return; 136 } 137 } 138 fps.put(internalName, fp); 139 return; 140 } 141 142 // process a versioned entry, look for previous entry with same name 143 FingerPrint matchFp = fps.get(internalName); 144 debug("looking for match"); 145 if (matchFp == null) { 146 debug("no match found"); 147 if (fp.isClass()) { 148 if (fp.isNestedClass()) { 149 if (!checkNestedClass(version, entryName, internalName, fp)) { 150 isValid = false; 151 } 152 return; 153 } 154 if (fp.isPublicClass()) { 155 main.error(Main.formatMsg("error.validator.new.public.class", entryName)); 156 isValid = false; 157 return; 158 } 159 debug("%s is a non-public class entry", entryName); 160 fps.put(internalName, fp); 161 currentTopLevelName = fp.topLevelName(); 162 return; 163 } 164 debug("%s is a resource entry"); 165 fps.put(internalName, fp); 166 return; 167 } 168 debug("match found"); 169 170 // are the two classes/resources identical? 171 if (fp.isIdentical(matchFp)) { 172 main.error(Main.formatMsg("error.validator.identical.entry", entryName)); 173 return; // it's okay, just takes up room 174 } 175 debug("sha1 not equal -- different bytes"); 176 177 // ok, not identical, check for compatible class version and api 178 if (fp.isClass()) { 179 if (fp.isNestedClass()) { 180 if (!checkNestedClass(version, entryName, internalName, fp)) { 181 isValid = false; 182 } 183 return; 184 } 185 debug("%s is a class entry", entryName); 186 if (!fp.isCompatibleVersion(matchFp)) { 187 main.error(Main.formatMsg("error.validator.incompatible.class.version", entryName)); 188 isValid = false; 189 return; 190 } 191 if (!fp.isSameAPI(matchFp)) { 192 main.error(Main.formatMsg("error.validator.different.api", entryName)); 193 isValid = false; 194 return; 195 } 196 if (!checkInternalName(entryName, basename, internalName)) { 197 isValid = false; 198 return; 199 } 200 debug("fingerprints same -- same api"); 201 fps.put(internalName, fp); 202 currentTopLevelName = fp.topLevelName(); 203 return; 204 } 205 debug("%s is a resource", entryName); 206 207 main.error(Main.formatMsg("error.validator.resources.with.same.name", entryName)); 208 fps.put(internalName, fp); 209 return; 210 } 211 212 private boolean checkInternalName(String entryName, String basename, String internalName) { 213 String className = className(basename); 214 if (internalName.equals(className)) { 215 return true; 216 } 217 main.error(Main.formatMsg2("error.validator.names.mismatch", 218 entryName, internalName.replace("/", "."))); 219 return false; 220 } 221 222 private boolean checkNestedClass(int version, String entryName, String internalName, FingerPrint fp) { 223 debug("%s is a nested class entry in top level class %s", entryName, fp.topLevelName()); 224 if (fp.topLevelName().equals(currentTopLevelName)) { 225 debug("%s (top level class) was accepted", fp.topLevelName()); 226 fps.put(internalName, fp); 227 return true; 228 } 229 debug("top level class was not accepted"); 230 main.error(Main.formatMsg("error.validator.isolated.nested.class", entryName)); 231 return false; 232 } 233 234 private String className(String entryName) { 235 return entryName.endsWith(".class") ? entryName.substring(0, entryName.length() - 6) : null; 236 } 237 238 private void debug(String fmt, Object... args) { 239 if (DEBUG) System.err.format(fmt, args); 240 } 241 } 242