Validator.java revision 15901:e340b25a9e59
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
26package sun.tools.jar;
27
28import java.io.IOException;
29import java.io.InputStream;
30import java.util.HashMap;
31import java.util.Map;
32import java.util.function.Consumer;
33import java.util.jar.JarEntry;
34import java.util.jar.JarFile;
35
36final 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                    if (!isConcealed(internalName)) {
156                        main.error(Main.formatMsg("error.validator.new.public.class", entryName));
157                        isValid = false;
158                        return;
159                    }
160                    main.warn(Main.formatMsg("warn.validator.concealed.public.class", entryName));
161                    debug("%s is a public class entry in a concealed package", entryName);
162                }
163                debug("%s is a non-public class entry", entryName);
164                fps.put(internalName, fp);
165                currentTopLevelName = fp.topLevelName();
166                return;
167            }
168            debug("%s is a resource entry");
169            fps.put(internalName, fp);
170            return;
171        }
172        debug("match found");
173
174        // are the two classes/resources identical?
175        if (fp.isIdentical(matchFp)) {
176            main.warn(Main.formatMsg("warn.validator.identical.entry", entryName));
177            return;  // it's okay, just takes up room
178        }
179        debug("sha1 not equal -- different bytes");
180
181        // ok, not identical, check for compatible class version and api
182        if (fp.isClass()) {
183            if (fp.isNestedClass()) {
184                if (!checkNestedClass(version, entryName, internalName, fp)) {
185                    isValid = false;
186                }
187                return;
188            }
189            debug("%s is a class entry", entryName);
190            if (!fp.isCompatibleVersion(matchFp)) {
191                main.error(Main.formatMsg("error.validator.incompatible.class.version", entryName));
192                isValid = false;
193                return;
194            }
195            if (!fp.isSameAPI(matchFp)) {
196                main.error(Main.formatMsg("error.validator.different.api", entryName));
197                isValid = false;
198                return;
199            }
200            if (!checkInternalName(entryName, basename, internalName)) {
201                isValid = false;
202                return;
203            }
204            debug("fingerprints same -- same api");
205            fps.put(internalName, fp);
206            currentTopLevelName = fp.topLevelName();
207            return;
208        }
209        debug("%s is a resource", entryName);
210
211        main.warn(Main.formatMsg("warn.validator.resources.with.same.name", entryName));
212        fps.put(internalName, fp);
213        return;
214    }
215
216    private boolean checkInternalName(String entryName, String basename, String internalName) {
217        String className = className(basename);
218        if (internalName.equals(className)) {
219            return true;
220        }
221        main.error(Main.formatMsg2("error.validator.names.mismatch",
222                entryName, internalName.replace("/", ".")));
223        return false;
224    }
225
226    private boolean checkNestedClass(int version, String entryName, String internalName, FingerPrint fp) {
227        debug("%s is a nested class entry in top level class %s", entryName, fp.topLevelName());
228        if (fp.topLevelName().equals(currentTopLevelName)) {
229            debug("%s (top level class) was accepted", fp.topLevelName());
230            fps.put(internalName, fp);
231            return true;
232        }
233        debug("top level class was not accepted");
234        main.error(Main.formatMsg("error.validator.isolated.nested.class", entryName));
235        return false;
236    }
237
238    private String className(String entryName) {
239        return entryName.endsWith(".class") ? entryName.substring(0, entryName.length() - 6) : null;
240    }
241
242    private boolean isConcealed(String internalName) {
243        if (main.concealedPackages.isEmpty()) {
244            return false;
245        }
246        int idx = internalName.lastIndexOf('/');
247        String pkgName = idx != -1 ? internalName.substring(0, idx).replace('/', '.') : "";
248        return main.concealedPackages.contains(pkgName);
249    }
250
251    private void debug(String fmt, Object... args) {
252        if (DEBUG) System.err.format(fmt, args);
253    }
254}
255
256