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      */
finalize()115     protected void finalize() throws IOException {
116         close();
117     }
118 
119     /**
120      * Returns the <code>ZipEntry</code> for the given entry name or
121      * <code>null</code> if not found.
122      *
123      * @param name the JAR file entry name
124      * @return the <code>ZipEntry</code> for the given entry name or
125      *         <code>null</code> if not found
126      * @see java.util.zip.ZipEntry
127      */
getEntry(String name)128     public ZipEntry getEntry(String name) {
129         ZipEntry ze = super.getEntry(name);
130         if (ze != null) {
131             if (ze instanceof JarEntry)
132                 return new URLJarFileEntry((JarEntry)ze);
133             else
134                 throw new InternalError(super.getClass() +
135                                         " returned unexpected entry type " +
136                                         ze.getClass());
137         }
138         return null;
139     }
140 
getManifest()141     public Manifest getManifest() throws IOException {
142 
143         if (!isSuperMan()) {
144             return null;
145         }
146 
147         Manifest man = new Manifest();
148         Attributes attr = man.getMainAttributes();
149         attr.putAll((Map)superAttr.clone());
150 
151         // now deep copy the manifest entries
152         if (superEntries != null) {
153             Map<String, Attributes> entries = man.getEntries();
154             for (String key : superEntries.keySet()) {
155                 Attributes at = superEntries.get(key);
156                 entries.put(key, (Attributes) at.clone());
157             }
158         }
159 
160         return man;
161     }
162 
163     /* If close controller is set the notify the controller about the pending close */
close()164     public void close() throws IOException {
165         if (closeController != null) {
166                 closeController.close(this);
167         }
168         super.close();
169     }
170 
171     // optimal side-effects
isSuperMan()172     private synchronized boolean isSuperMan() throws IOException {
173 
174         if (superMan == null) {
175             superMan = super.getManifest();
176         }
177 
178         if (superMan != null) {
179             superAttr = superMan.getMainAttributes();
180             superEntries = superMan.getEntries();
181             return true;
182         } else
183             return false;
184     }
185 
186     /**
187      * Given a URL, retrieves a JAR file, caches it to disk, and creates a
188      * cached JAR file object.
189      */
retrieve(final URL url)190     private static JarFile retrieve(final URL url) throws IOException {
191         return retrieve(url, null);
192     }
193 
194     /**
195      * Given a URL, retrieves a JAR file, caches it to disk, and creates a
196      * cached JAR file object.
197      */
retrieve(final URL url, final URLJarFileCloseController closeController)198      private static JarFile retrieve(final URL url, final URLJarFileCloseController closeController) throws IOException {
199 // Android-changed: Removed setCallBack(URLJarFileCallBack) and field callback (dead code).
200 //        /*
201 //         * See if interface is set, then call retrieve function of the class
202 //         * that implements URLJarFileCallBack interface (sun.plugin - to
203 //         * handle the cache failure for JARJAR file.)
204 //         */
205 //        if (callback != null)
206 //        {
207 //            return callback.retrieve(url);
208 //        }
209 //
210 //        else
211         {
212 
213             JarFile result = null;
214 
215             /* get the stream before asserting privileges */
216             try (final InputStream in = url.openConnection().getInputStream()) {
217                 result = AccessController.doPrivileged(
218                     new PrivilegedExceptionAction<JarFile>() {
219                         public JarFile run() throws IOException {
220                             Path tmpFile = Files.createTempFile("jar_cache", null);
221                             try {
222                                 Files.copy(in, tmpFile, StandardCopyOption.REPLACE_EXISTING);
223                                 JarFile jarFile = new URLJarFile(tmpFile.toFile(), closeController);
224                                 tmpFile.toFile().deleteOnExit();
225                                 return jarFile;
226                             } catch (Throwable thr) {
227                                 try {
228                                     Files.delete(tmpFile);
229                                 } catch (IOException ioe) {
230                                     thr.addSuppressed(ioe);
231                                 }
232                                 throw thr;
233                             }
234                         }
235                     });
236             } catch (PrivilegedActionException pae) {
237                 throw (IOException) pae.getException();
238             }
239 
240             return result;
241         }
242     }
243 
244 // Android-changed: Removed setCallBack(URLJarFileCallBack) and field callback (dead code).
245 //    /*
246 //     * Set the call back interface to call retrive function in sun.plugin
247 //     * package if plugin is running.
248 //     */
249 //    public static void setCallBack(URLJarFileCallBack cb)
250 //    {
251 //        callback = cb;
252 //    }
253 
254     private class URLJarFileEntry extends JarEntry {
255         private JarEntry je;
256 
URLJarFileEntry(JarEntry je)257         URLJarFileEntry(JarEntry je) {
258             super(je);
259             this.je=je;
260         }
261 
getAttributes()262         public Attributes getAttributes() throws IOException {
263             if (URLJarFile.this.isSuperMan()) {
264                 Map<String, Attributes> e = URLJarFile.this.superEntries;
265                 if (e != null) {
266                     Attributes a = e.get(getName());
267                     if (a != null)
268                         return  (Attributes)a.clone();
269                 }
270             }
271             return null;
272         }
273 
getCertificates()274         public java.security.cert.Certificate[] getCertificates() {
275             Certificate[] certs = je.getCertificates();
276             return certs == null? null: certs.clone();
277         }
278 
getCodeSigners()279         public CodeSigner[] getCodeSigners() {
280             CodeSigner[] csg = je.getCodeSigners();
281             return csg == null? null: csg.clone();
282         }
283     }
284 
285     public interface URLJarFileCloseController {
close(JarFile jarFile)286         public void close(JarFile jarFile);
287     }
288 }
289