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.InvalidModuleDescriptorException;
  32 import java.lang.module.ModuleDescriptor;
  33 import java.lang.module.ModuleDescriptor.Exports;
  34 import java.lang.module.InvalidModuleDescriptorException;
  35 import java.lang.module.ModuleDescriptor.Opens;
  36 import java.lang.module.ModuleDescriptor.Provides;
  37 import java.lang.module.ModuleDescriptor.Requires;
  38 import java.util.Collections;
  39 import java.util.Comparator;
  40 import java.util.HashMap;
  41 import java.util.HashSet;
  42 import java.util.List;
  43 import java.util.Map;
  44 import java.util.Set;
  45 import java.util.function.Consumer;
  46 import java.util.jar.JarEntry;
  47 import java.util.jar.JarFile;
  48 import java.util.zip.ZipEntry;
  49 
  50 import static java.util.jar.JarFile.MANIFEST_NAME;
  51 import static sun.tools.jar.Main.VERSIONS_DIR;
  52 import static sun.tools.jar.Main.VERSIONS_DIR_LENGTH;
  53 import static sun.tools.jar.Main.MODULE_INFO;
  54 import static sun.tools.jar.Main.getMsg;
  55 import static sun.tools.jar.Main.formatMsg;
  56 import static sun.tools.jar.Main.formatMsg2;
  57 import static sun.tools.jar.Main.toBinaryName;
  58 import static sun.tools.jar.Main.isModuleInfoEntry;
  59 
  60 final class Validator {
  61     private final static boolean DEBUG = Boolean.getBoolean("jar.debug");
  62     private final  Map<String,FingerPrint> fps = new HashMap<>();
  63     private final Main main;
  64     private final JarFile jf;
  65     private int oldVersion = -1;
  66     private String currentTopLevelName;
  67     private boolean isValid = true;
  68     private Set<String> concealedPkgs = Collections.emptySet();
  69     private ModuleDescriptor md;
  70     private String mdName;
  71 
  72     private Validator(Main main, JarFile jf) {
  73         this.main = main;
  74         this.jf = jf;
  75         checkModuleDescriptor(MODULE_INFO);
  76     }
  77 
  78     static boolean validate(Main main, JarFile jf) throws IOException {
  79         return new Validator(main, jf).validate();
  80     }
  81 
  82     private boolean validate() {
  83         try {
  84             jf.stream()
  85               .filter(e -> !e.isDirectory() &&
  86                       !e.getName().equals(MANIFEST_NAME))
  87               .sorted(ENTRY_COMPARATOR)
  88               .forEachOrdered(e -> validate(e));
  89             return isValid;
  90         } catch (InvalidJarException e) {
  91             error(formatMsg("error.validator.bad.entry.name", e.getMessage()));
  92         }
  93         return false;
  94     }
  95 
  96     private static class InvalidJarException extends RuntimeException {
  97         private static final long serialVersionUID = -3642329147299217726L;
  98         InvalidJarException(String msg) {
  99             super(msg);
 100         }
 101     }
 102 
 103     // sort base entries before versioned entries, and sort entry classes with
 104     // nested classes so that the top level class appears before the associated
 105     // nested class
 106     static Comparator<String> ENTRYNAME_COMPARATOR = (s1, s2) ->  {
 107 
 108         if (s1.equals(s2)) return 0;
 109         boolean b1 = s1.startsWith(VERSIONS_DIR);
 110         boolean b2 = s2.startsWith(VERSIONS_DIR);
 111         if (b1 && !b2) return 1;
 112         if (!b1 && b2) return -1;
 113         int n = 0; // starting char for String compare
 114         if (b1 && b2) {
 115             // normally strings would be sorted so "10" goes before "9", but
 116             // version number strings need to be sorted numerically
 117             n = VERSIONS_DIR.length();   // skip the common prefix
 118             int i1 = s1.indexOf('/', n);
 119             int i2 = s2.indexOf('/', n);
 120             if (i1 == -1) throw new InvalidJarException(s1);
 121             if (i2 == -1) throw new InvalidJarException(s2);
 122             // shorter version numbers go first
 123             if (i1 != i2) return i1 - i2;
 124             // otherwise, handle equal length numbers below
 125         }
 126         int l1 = s1.length();
 127         int l2 = s2.length();
 128         int lim = Math.min(l1, l2);
 129         for (int k = n; k < lim; k++) {
 130             char c1 = s1.charAt(k);
 131             char c2 = s2.charAt(k);
 132             if (c1 != c2) {
 133                 // change natural ordering so '.' comes before '$'
 134                 // i.e. top level classes come before nested classes
 135                 if (c1 == '$' && c2 == '.') return 1;
 136                 if (c1 == '.' && c2 == '$') return -1;
 137                 return c1 - c2;
 138             }
 139         }
 140         return l1 - l2;
 141     };
 142 
 143     static Comparator<ZipEntry> ENTRY_COMPARATOR =
 144         Comparator.comparing(ZipEntry::getName, ENTRYNAME_COMPARATOR);
 145 
 146     /*
 147      *  Validator has state and assumes entries provided to accept are ordered
 148      *  from base entries first and then through the versioned entries in
 149      *  ascending version order.  Also, to find isolated nested classes,
 150      *  classes must be ordered so that the top level class is before the associated
 151      *  nested class(es).
 152     */
 153     public void validate(JarEntry je) {
 154         String entryName = je.getName();
 155 
 156         // directories are always accepted
 157         if (entryName.endsWith("/")) {
 158             debug("%s is a directory", entryName);
 159             return;
 160         }
 161 
 162         // validate the versioned module-info
 163         if (isModuleInfoEntry(entryName)) {
 164             if (!entryName.equals(mdName))
 165                 checkModuleDescriptor(entryName);
 166             return;
 167         }
 168 
 169         // figure out the version and basename from the JarEntry
 170         int version;
 171         String basename;
 172         String versionStr = null;;
 173         if (entryName.startsWith(VERSIONS_DIR)) {
 174             int n = entryName.indexOf("/", VERSIONS_DIR_LENGTH);
 175             if (n == -1) {
 176                 error(formatMsg("error.validator.version.notnumber", entryName));
 177                 isValid = false;
 178                 return;
 179             }
 180             versionStr = entryName.substring(VERSIONS_DIR_LENGTH, n);
 181             try {
 182                 version = Integer.parseInt(versionStr);
 183             } catch (NumberFormatException x) {
 184                 error(formatMsg("error.validator.version.notnumber", entryName));
 185                 isValid = false;
 186                 return;
 187             }
 188             if (n == entryName.length()) {
 189                 error(formatMsg("error.validator.entryname.tooshort", entryName));
 190                 isValid = false;
 191                 return;
 192             }
 193             basename = entryName.substring(n + 1);
 194         } else {
 195             version = 0;
 196             basename = entryName;
 197         }
 198         debug("\n===================\nversion %d %s", version, entryName);
 199 
 200         if (oldVersion != version) {
 201             oldVersion = version;
 202             currentTopLevelName = null;
 203             if (md == null && versionStr != null) {
 204                 // don't have a base module-info.class yet, try to see if
 205                 // a versioned one exists
 206                 checkModuleDescriptor(VERSIONS_DIR + versionStr + "/" + MODULE_INFO);
 207             }
 208         }
 209 
 210         // analyze the entry, keeping key attributes
 211         FingerPrint fp;
 212         try (InputStream is = jf.getInputStream(je)) {
 213             fp = new FingerPrint(basename, is.readAllBytes());
 214         } catch (IOException x) {
 215             error(x.getMessage());
 216             isValid = false;
 217             return;
 218         }
 219         String internalName = fp.name();
 220 
 221         // process a base entry paying attention to nested classes
 222         if (version == 0) {
 223             debug("base entry found");
 224             if (fp.isNestedClass()) {
 225                 debug("nested class found");
 226                 if (fp.topLevelName().equals(currentTopLevelName)) {
 227                     fps.put(internalName, fp);
 228                     return;
 229                 }
 230                 error(formatMsg("error.validator.isolated.nested.class", entryName));
 231                 isValid = false;
 232                 return;
 233             }
 234             // top level class or resource entry
 235             if (fp.isClass()) {
 236                 currentTopLevelName = fp.topLevelName();
 237                 if (!checkInternalName(entryName, basename, internalName)) {
 238                     isValid = false;
 239                     return;
 240                 }
 241             }
 242             fps.put(internalName, fp);
 243             return;
 244         }
 245 
 246         // process a versioned entry, look for previous entry with same name
 247         FingerPrint matchFp = fps.get(internalName);
 248         debug("looking for match");
 249         if (matchFp == null) {
 250             debug("no match found");
 251             if (fp.isClass()) {
 252                 if (fp.isNestedClass()) {
 253                     if (!checkNestedClass(version, entryName, internalName, fp)) {
 254                         isValid = false;
 255                     }
 256                     return;
 257                 }
 258                 if (fp.isPublicClass()) {
 259                     if (!isConcealed(internalName)) {
 260                         error(Main.formatMsg("error.validator.new.public.class", entryName));
 261                         isValid = false;
 262                         return;
 263                     }
 264                     warn(formatMsg("warn.validator.concealed.public.class", entryName));
 265                     debug("%s is a public class entry in a concealed package", entryName);
 266                 }
 267                 debug("%s is a non-public class entry", entryName);
 268                 fps.put(internalName, fp);
 269                 currentTopLevelName = fp.topLevelName();
 270                 return;
 271             }
 272             debug("%s is a resource entry");
 273             fps.put(internalName, fp);
 274             return;
 275         }
 276         debug("match found");
 277 
 278         // are the two classes/resources identical?
 279         if (fp.isIdentical(matchFp)) {
 280             warn(formatMsg("warn.validator.identical.entry", entryName));
 281             return;  // it's okay, just takes up room
 282         }
 283         debug("sha1 not equal -- different bytes");
 284 
 285         // ok, not identical, check for compatible class version and api
 286         if (fp.isClass()) {
 287             if (fp.isNestedClass()) {
 288                 if (!checkNestedClass(version, entryName, internalName, fp)) {
 289                     isValid = false;
 290                 }
 291                 return;
 292             }
 293             debug("%s is a class entry", entryName);
 294             if (!fp.isCompatibleVersion(matchFp)) {
 295                 error(formatMsg("error.validator.incompatible.class.version", entryName));
 296                 isValid = false;
 297                 return;
 298             }
 299             if (!fp.isSameAPI(matchFp)) {
 300                 error(formatMsg("error.validator.different.api", entryName));
 301                 isValid = false;
 302                 return;
 303             }
 304             if (!checkInternalName(entryName, basename, internalName)) {
 305                 isValid = false;
 306                 return;
 307             }
 308             debug("fingerprints same -- same api");
 309             fps.put(internalName, fp);
 310             currentTopLevelName = fp.topLevelName();
 311             return;
 312         }
 313         debug("%s is a resource", entryName);
 314 
 315         warn(formatMsg("warn.validator.resources.with.same.name", entryName));
 316         fps.put(internalName, fp);
 317         return;
 318     }
 319 
 320     /**
 321      * Checks whether or not the given versioned module descriptor's attributes
 322      * are valid when compared against the root/base module descriptor.
 323      *
 324      * A versioned module descriptor must be identical to the root/base module
 325      * descriptor, with two exceptions:
 326      *  - A versioned descriptor can have different non-public `requires`
 327      *    clauses of platform ( `java.*` and `jdk.*` ) modules, and
 328      *  - A versioned descriptor can have different `uses` clauses, even of
 329      *    service types defined outside of the platform modules.
 330      */
 331     private void checkModuleDescriptor(String miName) {
 332         ZipEntry je = jf.getEntry(miName);
 333         if (je != null) {
 334             try (InputStream jis = jf.getInputStream(je)) {
 335                 ModuleDescriptor md = ModuleDescriptor.read(jis);
 336                 // Initialize the base md if it's not yet. A "base" md can be either the
 337                 // root module-info.class or the first versioned module-info.class
 338                 ModuleDescriptor base = this.md;
 339 
 340                 if (base == null) {
 341                     concealedPkgs = new HashSet<>(md.packages());
 342                     md.exports().stream().map(Exports::source).forEach(concealedPkgs::remove);
 343                     md.opens().stream().map(Opens::source).forEach(concealedPkgs::remove);
 344                     // must have the implementation class of the services it 'provides'.
 345                     if (md.provides().stream().map(Provides::providers)
 346                           .flatMap(List::stream)
 347                           .filter(p -> jf.getEntry(toBinaryName(p)) == null)
 348                           .peek(p -> error(formatMsg("error.missing.provider", p)))
 349                           .count() != 0) {
 350                         isValid = false;
 351                         return;
 352                     }
 353                     this.md = md;
 354                     this.mdName = miName;
 355                     return;
 356                 }
 357 
 358                 if (!base.name().equals(md.name())) {
 359                     error(getMsg("error.validator.info.name.notequal"));
 360                     isValid = false;
 361                 }
 362                 if (!base.requires().equals(md.requires())) {
 363                     Set<Requires> baseRequires = base.requires();
 364                     for (Requires r : md.requires()) {
 365                         if (baseRequires.contains(r))
 366                             continue;
 367                         if (r.modifiers().contains(Requires.Modifier.TRANSITIVE)) {
 368                             error(getMsg("error.validator.info.requires.transitive"));
 369                             isValid = false;
 370                         } else if (!isPlatformModule(r.name())) {
 371                             error(getMsg("error.validator.info.requires.added"));
 372                             isValid = false;
 373                         }
 374                     }
 375                     for (Requires r : baseRequires) {
 376                         Set<Requires> mdRequires = md.requires();
 377                         if (mdRequires.contains(r))
 378                             continue;
 379                         if (!isPlatformModule(r.name())) {
 380                             error(getMsg("error.validator.info.requires.dropped"));
 381                             isValid = false;
 382                         }
 383                     }
 384                 }
 385                 if (!base.exports().equals(md.exports())) {
 386                     error(getMsg("error.validator.info.exports.notequal"));
 387                     isValid = false;
 388                 }
 389                 if (!base.opens().equals(md.opens())) {
 390                     error(getMsg("error.validator.info.opens.notequal"));
 391                     isValid = false;
 392                 }
 393                 if (!base.provides().equals(md.provides())) {
 394                     error(getMsg("error.validator.info.provides.notequal"));
 395                     isValid = false;
 396                 }
 397                 if (!base.mainClass().equals(md.mainClass())) {
 398                     error(formatMsg("error.validator.info.manclass.notequal", je.getName()));
 399                     isValid = false;
 400                 }
 401                 if (!base.version().equals(md.version())) {
 402                     error(formatMsg("error.validator.info.version.notequal", je.getName()));
 403                     isValid = false;
 404                 }
 405             } catch (Exception x) {
 406                 error(x.getMessage() + " : " + miName);
 407                 this.isValid = false;
 408             }
 409         }
 410     }
 411 
 412     private static boolean isPlatformModule(String name) {
 413         return name.startsWith("java.") || name.startsWith("jdk.");
 414     }
 415 
 416     private boolean checkInternalName(String entryName, String basename, String internalName) {
 417         String className = className(basename);
 418         if (internalName.equals(className)) {
 419             return true;
 420         }
 421         error(formatMsg2("error.validator.names.mismatch",
 422                 entryName, internalName.replace("/", ".")));
 423         return false;
 424     }
 425 
 426     private boolean checkNestedClass(int version, String entryName, String internalName, FingerPrint fp) {
 427         debug("%s is a nested class entry in top level class %s", entryName, fp.topLevelName());
 428         if (fp.topLevelName().equals(currentTopLevelName)) {
 429             debug("%s (top level class) was accepted", fp.topLevelName());
 430             fps.put(internalName, fp);
 431             return true;
 432         }
 433         debug("top level class was not accepted");
 434         error(formatMsg("error.validator.isolated.nested.class", entryName));
 435         return false;
 436     }
 437 
 438     private String className(String entryName) {
 439         return entryName.endsWith(".class") ? entryName.substring(0, entryName.length() - 6) : null;
 440     }
 441 
 442     private boolean isConcealed(String internalName) {
 443         if (concealedPkgs.isEmpty()) {
 444             return false;
 445         }
 446         int idx = internalName.lastIndexOf('/');
 447         String pkgName = idx != -1 ? internalName.substring(0, idx).replace('/', '.') : "";
 448         return concealedPkgs.contains(pkgName);
 449     }
 450 
 451     private void debug(String fmt, Object... args) {
 452         if (DEBUG) System.err.format(fmt, args);
 453     }
 454 
 455     private void error(String msg) {
 456         main.error(msg);
 457     }
 458 
 459     private void warn(String msg) {
 460         main.warn(msg);
 461     }
 462 
 463 }