1 /*
   2  * Copyright (c) 2001, 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 
  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             Runtime.Version version = "runtime".equals(url.getRef())
  70                     ? JarFile.runtimeVersion()
  71                     : JarFile.baseVersion();
  72             return new URLJarFile(url, closeController, version);
  73         } else {
  74             return retrieve(url, closeController);
  75         }
  76     }
  77 
  78     /*
  79      * Changed modifier from private to public in order to be able
  80      * to instantiate URLJarFile from sun.plugin package.
  81      */
  82     public URLJarFile(File file) throws IOException {
  83         this(file, null);
  84     }
  85 
  86     /*
  87      * Changed modifier from private to public in order to be able
  88      * to instantiate URLJarFile from sun.plugin package.
  89      */
  90     public URLJarFile(File file, URLJarFileCloseController closeController) throws IOException {
  91         super(file, true, ZipFile.OPEN_READ | ZipFile.OPEN_DELETE);
  92         this.closeController = closeController;
  93     }
  94 
  95     private URLJarFile(File file, URLJarFileCloseController closeController, Runtime.Version version)
  96             throws IOException {
  97         super(file, true, ZipFile.OPEN_READ | ZipFile.OPEN_DELETE, version);
  98         this.closeController = closeController;
  99     }
 100 
 101     private URLJarFile(URL url, URLJarFileCloseController closeController, Runtime.Version version)
 102             throws IOException {
 103         super(new File(ParseUtil.decode(url.getFile())), true, ZipFile.OPEN_READ, version);
 104         this.closeController = closeController;
 105     }
 106 
 107     private static boolean isFileURL(URL url) {
 108         if (url.getProtocol().equalsIgnoreCase("file")) {
 109             /*
 110              * Consider this a 'file' only if it's a LOCAL file, because
 111              * 'file:' URLs can be accessible through ftp.
 112              */
 113             String host = url.getHost();
 114             if (host == null || host.equals("") || host.equals("~") ||
 115                 host.equalsIgnoreCase("localhost"))
 116                 return true;
 117         }
 118         return false;
 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      */
 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 
 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 */
 166     public void close() throws IOException {
 167         if (closeController != null) {
 168                 closeController.close(this);
 169         }
 170         super.close();
 171     }
 172 
 173     // optimal side-effects
 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      */
 192      private static JarFile retrieve(final URL url, final URLJarFileCloseController closeController) throws IOException {
 193         /*
 194          * See if interface is set, then call retrieve function of the class
 195          * that implements URLJarFileCallBack interface (sun.plugin - to
 196          * handle the cache failure for JARJAR file.)
 197          */
 198         if (callback != null)
 199         {
 200             return callback.retrieve(url);
 201         }
 202 
 203         else
 204         {
 205 
 206             JarFile result = null;
 207             Runtime.Version version = "runtime".equals(url.getRef())
 208                     ? JarFile.runtimeVersion()
 209                     : JarFile.baseVersion();
 210 
 211             /* get the stream before asserting privileges */
 212             try (final InputStream in = url.openConnection().getInputStream()) {
 213                 result = AccessController.doPrivileged(
 214                     new PrivilegedExceptionAction<>() {
 215                         public JarFile run() throws IOException {
 216                             Path tmpFile = Files.createTempFile("jar_cache", null);
 217                             try {
 218                                 Files.copy(in, tmpFile, StandardCopyOption.REPLACE_EXISTING);
 219                                 JarFile jarFile = new URLJarFile(tmpFile.toFile(), closeController, version);
 220                                 tmpFile.toFile().deleteOnExit();
 221                                 return jarFile;
 222                             } catch (Throwable thr) {
 223                                 try {
 224                                     Files.delete(tmpFile);
 225                                 } catch (IOException ioe) {
 226                                     thr.addSuppressed(ioe);
 227                                 }
 228                                 throw thr;
 229                             }
 230                         }
 231                     });
 232             } catch (PrivilegedActionException pae) {
 233                 throw (IOException) pae.getException();
 234             }
 235 
 236             return result;
 237         }
 238     }
 239 
 240     /*
 241      * Set the call back interface to call retrive function in sun.plugin
 242      * package if plugin is running.
 243      */
 244     public static void setCallBack(URLJarFileCallBack cb)
 245     {
 246         callback = cb;
 247     }
 248 
 249 
 250     private class URLJarFileEntry extends JarEntry {
 251         private JarEntry je;
 252 
 253         URLJarFileEntry(JarEntry je) {
 254             super(je);
 255             this.je=je;
 256         }
 257 
 258         public Attributes getAttributes() throws IOException {
 259             if (URLJarFile.this.isSuperMan()) {
 260                 Map<String, Attributes> e = URLJarFile.this.superEntries;
 261                 if (e != null) {
 262                     Attributes a = e.get(getName());
 263                     if (a != null)
 264                         return  (Attributes)a.clone();
 265                 }
 266             }
 267             return null;
 268         }
 269 
 270         public java.security.cert.Certificate[] getCertificates() {
 271             Certificate[] certs = je.getCertificates();
 272             return certs == null? null: certs.clone();
 273         }
 274 
 275         public CodeSigner[] getCodeSigners() {
 276             CodeSigner[] csg = je.getCodeSigners();
 277             return csg == null? null: csg.clone();
 278         }
 279     }
 280 
 281     public interface URLJarFileCloseController {
 282         public void close(JarFile jarFile);
 283     }
 284 }