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