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 }