1 /*
2  * Copyright (c) 2001, 2011, 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.net.www.protocol.jar;
27 
28 import java.io.*;
29 import java.net.*;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.nio.file.StandardCopyOption;
33 import java.util.*;
34 import java.util.jar.*;
35 import java.util.zip.ZipFile;
36 import java.util.zip.ZipEntry;
37 import java.security.CodeSigner;
38 import java.security.cert.Certificate;
39 import java.security.AccessController;
40 import java.security.PrivilegedAction;
41 import java.security.PrivilegedExceptionAction;
42 import java.security.PrivilegedActionException;
43 import sun.net.www.ParseUtil;
44 
45 /* URL jar file is a common JarFile subtype used for JarURLConnection */
46 public class URLJarFile extends JarFile {
47 
48 // Android-changed: Removed setCallBack(URLJarFileCallBack) and field callback (dead code).
49 //    /*
50 //     * Interface to be able to call retrieve() in plugin if
51 //     * this variable is set.
52 //     */
53 //    private static URLJarFileCallBack callback = null;
54 
55     /* Controller of the Jar File's closing */
56     private URLJarFileCloseController closeController = null;
57 
58     private static int BUF_SIZE = 2048;
59 
60     private Manifest superMan;
61     private Attributes superAttr;
62     private Map<String, Attributes> superEntries;
63 
getJarFile(URL url)64     static JarFile getJarFile(URL url) throws IOException {
65         return getJarFile(url, null);
66     }
67 
getJarFile(URL url, URLJarFileCloseController closeController)68     static JarFile getJarFile(URL url, URLJarFileCloseController closeController) throws IOException {
69         if (isFileURL(url))
70             return new URLJarFile(url, closeController);
71         else {
72             return retrieve(url, closeController);
73         }
74     }
75 
76     /*
77      * Changed modifier from private to public in order to be able
78      * to instantiate URLJarFile from sun.plugin package.
79      */
URLJarFile(File file)80     public URLJarFile(File file) throws IOException {
81         this(file, null);
82     }
83 
84     /*
85      * Changed modifier from private to public in order to be able
86      * to instantiate URLJarFile from sun.plugin package.
87      */
URLJarFile(File file, URLJarFileCloseController closeController)88     public URLJarFile(File file, URLJarFileCloseController closeController) throws IOException {
89         super(file, true, ZipFile.OPEN_READ | ZipFile.OPEN_DELETE);
90         this.closeController = closeController;
91     }
92 
URLJarFile(URL url, URLJarFileCloseController closeController)93     private URLJarFile(URL url, URLJarFileCloseController closeController) throws IOException {
94         super(ParseUtil.decode(url.getFile()));
95         this.closeController = closeController;
96     }
97 
isFileURL(URL url)98     private static boolean isFileURL(URL url) {
99         if (url.getProtocol().equalsIgnoreCase("file")) {
100             /*
101              * Consider this a 'file' only if it's a LOCAL file, because
102              * 'file:' URLs can be accessible through ftp.
103              */
104             String host = url.getHost();
105             if (host == null || host.equals("") || host.equals("~") ||
106                 host.equalsIgnoreCase("localhost"))
107                 return true;
108         }
109         return false;
110     }
111 
112     /*
113      * close the jar file.
114      */
115     // Android-note: All important methods here and in superclasses should synchronize on this
116     // to avoid premature finalization. The actual close implementation in ZipFile does.
finalize()117     protected void finalize() throws IOException {
118         close();
119     }
120 
121     /**
122      * Returns the <code>ZipEntry</code> for the given entry name or
123      * <code>null</code> if not found.
124      *
125      * @param name the JAR file entry name
126      * @return the <code>ZipEntry</code> for the given entry name or
127      *         <code>null</code> if not found
128      * @see java.util.zip.ZipEntry
129      */
getEntry(String name)130     public ZipEntry getEntry(String name) {
131         ZipEntry ze = super.getEntry(name);
132         if (ze != null) {
133             if (ze instanceof JarEntry)
134                 return new URLJarFileEntry((JarEntry)ze);
135             else
136                 throw new InternalError(super.getClass() +
137                                         " returned unexpected entry type " +
138                                         ze.getClass());
139         }
140         return null;
141     }
142 
getManifest()143     public Manifest getManifest() throws IOException {
144 
145         if (!isSuperMan()) {
146             return null;
147         }
148 
149         Manifest man = new Manifest();
150         Attributes attr = man.getMainAttributes();
151         attr.putAll((Map)superAttr.clone());
152 
153         // now deep copy the manifest entries
154         if (superEntries != null) {
155             Map<String, Attributes> entries = man.getEntries();
156             for (String key : superEntries.keySet()) {
157                 Attributes at = superEntries.get(key);
158                 entries.put(key, (Attributes) at.clone());
159             }
160         }
161 
162         return man;
163     }
164 
165     /* If close controller is set the notify the controller about the pending close */
close()166     public void close() throws IOException {
167         if (closeController != null) {
168                 closeController.close(this);
169         }
170         super.close();
171     }
172 
173     // optimal side-effects
isSuperMan()174     private synchronized boolean isSuperMan() throws IOException {
175 
176         if (superMan == null) {
177             superMan = super.getManifest();
178         }
179 
180         if (superMan != null) {
181             superAttr = superMan.getMainAttributes();
182             superEntries = superMan.getEntries();
183             return true;
184         } else
185             return false;
186     }
187 
188     /**
189      * Given a URL, retrieves a JAR file, caches it to disk, and creates a
190      * cached JAR file object.
191      */
retrieve(final URL url)192     private static JarFile retrieve(final URL url) throws IOException {
193         return retrieve(url, null);
194     }
195 
196     /**
197      * Given a URL, retrieves a JAR file, caches it to disk, and creates a
198      * cached JAR file object.
199      */
retrieve(final URL url, final URLJarFileCloseController closeController)200      private static JarFile retrieve(final URL url, final URLJarFileCloseController closeController) throws IOException {
201 // Android-changed: Removed setCallBack(URLJarFileCallBack) and field callback (dead code).
202 //        /*
203 //         * See if interface is set, then call retrieve function of the class
204 //         * that implements URLJarFileCallBack interface (sun.plugin - to
205 //         * handle the cache failure for JARJAR file.)
206 //         */
207 //        if (callback != null)
208 //        {
209 //            return callback.retrieve(url);
210 //        }
211 //
212 //        else
213         {
214 
215             JarFile result = null;
216 
217             /* get the stream before asserting privileges */
218             try (final InputStream in = url.openConnection().getInputStream()) {
219                 result = AccessController.doPrivileged(
220                     new PrivilegedExceptionAction<JarFile>() {
221                         public JarFile run() throws IOException {
222                             Path tmpFile = Files.createTempFile("jar_cache", null);
223                             try {
224                                 Files.copy(in, tmpFile, StandardCopyOption.REPLACE_EXISTING);
225                                 JarFile jarFile = new URLJarFile(tmpFile.toFile(), closeController);
226                                 tmpFile.toFile().deleteOnExit();
227                                 return jarFile;
228                             } catch (Throwable thr) {
229                                 try {
230                                     Files.delete(tmpFile);
231                                 } catch (IOException ioe) {
232                                     thr.addSuppressed(ioe);
233                                 }
234                                 throw thr;
235                             }
236                         }
237                     });
238             } catch (PrivilegedActionException pae) {
239                 throw (IOException) pae.getException();
240             }
241 
242             return result;
243         }
244     }
245 
246 // Android-changed: Removed setCallBack(URLJarFileCallBack) and field callback (dead code).
247 //    /*
248 //     * Set the call back interface to call retrive function in sun.plugin
249 //     * package if plugin is running.
250 //     */
251 //    public static void setCallBack(URLJarFileCallBack cb)
252 //    {
253 //        callback = cb;
254 //    }
255 
256     private class URLJarFileEntry extends JarEntry {
257         private JarEntry je;
258 
URLJarFileEntry(JarEntry je)259         URLJarFileEntry(JarEntry je) {
260             super(je);
261             this.je=je;
262         }
263 
getAttributes()264         public Attributes getAttributes() throws IOException {
265             if (URLJarFile.this.isSuperMan()) {
266                 Map<String, Attributes> e = URLJarFile.this.superEntries;
267                 if (e != null) {
268                     Attributes a = e.get(getName());
269                     if (a != null)
270                         return  (Attributes)a.clone();
271                 }
272             }
273             return null;
274         }
275 
getCertificates()276         public java.security.cert.Certificate[] getCertificates() {
277             Certificate[] certs = je.getCertificates();
278             return certs == null? null: certs.clone();
279         }
280 
getCodeSigners()281         public CodeSigner[] getCodeSigners() {
282             CodeSigner[] csg = je.getCodeSigners();
283             return csg == null? null: csg.clone();
284         }
285     }
286 
287     public interface URLJarFileCloseController {
close(JarFile jarFile)288         public void close(JarFile jarFile);
289     }
290 }
291