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