Home | History | Annotate | Download | only in url
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 package libcore.net.url;
     19 
     20 import java.io.File;
     21 import java.io.FileNotFoundException;
     22 import java.io.FileOutputStream;
     23 import java.io.FilterInputStream;
     24 import java.io.IOException;
     25 import java.io.InputStream;
     26 import java.net.ContentHandler;
     27 import java.net.ContentHandlerFactory;
     28 import java.net.JarURLConnection;
     29 import java.net.MalformedURLException;
     30 import java.net.URL;
     31 import java.security.Permission;
     32 import java.util.HashMap;
     33 import java.util.Iterator;
     34 import java.util.Map;
     35 import java.util.Set;
     36 import java.util.jar.JarEntry;
     37 import java.util.jar.JarFile;
     38 import java.util.zip.ZipFile;
     39 import libcore.net.UriCodec;
     40 
     41 /**
     42  * This subclass extends {@code URLConnection}.
     43  * <p>
     44  *
     45  * This class is responsible for connecting and retrieving resources from a Jar
     46  * file which can be anywhere that can be referred to by an URL.
     47  */
     48 public class JarURLConnectionImpl extends JarURLConnection {
     49 
     50     static HashMap<URL, JarFile> jarCache = new HashMap<URL, JarFile>();
     51 
     52     private URL jarFileURL;
     53 
     54     private InputStream jarInput;
     55 
     56     private JarFile jarFile;
     57 
     58     private JarEntry jarEntry;
     59 
     60     private boolean closed;
     61 
     62     /**
     63      * @param url
     64      *            the URL of the JAR
     65      * @throws MalformedURLException
     66      *             if the URL is malformed
     67      * @throws IOException
     68      *             if there is a problem opening the connection.
     69      */
     70     public JarURLConnectionImpl(URL url) throws MalformedURLException,
     71             IOException {
     72         super(url);
     73         jarFileURL = getJarFileURL();
     74         jarFileURLConnection = jarFileURL.openConnection();
     75     }
     76 
     77     /**
     78      * @see java.net.URLConnection#connect()
     79      */
     80     @Override
     81     public void connect() throws IOException {
     82         if (!connected) {
     83             findJarFile(); // ensure the file can be found
     84             findJarEntry(); // ensure the entry, if any, can be found
     85             connected = true;
     86         }
     87     }
     88 
     89     /**
     90      * Returns the Jar file referred by this {@code URLConnection}.
     91      *
     92      * @return the JAR file referenced by this connection
     93      *
     94      * @throws IOException
     95      *             thrown if an IO error occurs while connecting to the
     96      *             resource.
     97      */
     98     @Override
     99     public JarFile getJarFile() throws IOException {
    100         connect();
    101         return jarFile;
    102     }
    103 
    104     /**
    105      * Returns the Jar file referred by this {@code URLConnection}
    106      *
    107      * @throws IOException
    108      *             if an IO error occurs while connecting to the resource.
    109      */
    110     private void findJarFile() throws IOException {
    111         JarFile jar = null;
    112         if (getUseCaches()) {
    113             synchronized (jarCache) {
    114                 jarFile = jarCache.get(jarFileURL);
    115             }
    116             if (jarFile == null) {
    117                 jar = openJarFile();
    118                 synchronized (jarCache) {
    119                     jarFile = jarCache.get(jarFileURL);
    120                     if (jarFile == null) {
    121                         jarCache.put(jarFileURL, jar);
    122                         jarFile = jar;
    123                     } else {
    124                         jar.close();
    125                     }
    126                 }
    127             }
    128         } else {
    129             jarFile = openJarFile();
    130         }
    131 
    132         if (jarFile == null) {
    133             throw new IOException();
    134         }
    135     }
    136 
    137     JarFile openJarFile() throws IOException {
    138         if (jarFileURL.getProtocol().equals("file")) {
    139             String decodedFile = UriCodec.decode(jarFileURL.getFile());
    140             return new JarFile(new File(decodedFile), true, ZipFile.OPEN_READ);
    141         } else {
    142             final InputStream is = jarFileURL.openConnection().getInputStream();
    143             try {
    144                 FileOutputStream fos = null;
    145                 JarFile result = null;
    146                 try {
    147                     File tempJar = File.createTempFile("hyjar_", ".tmp", null);
    148                     tempJar.deleteOnExit();
    149                     fos = new FileOutputStream(tempJar);
    150                     byte[] buf = new byte[4096];
    151                     int nbytes = 0;
    152                     while ((nbytes = is.read(buf)) > -1) {
    153                         fos.write(buf, 0, nbytes);
    154                     }
    155                     fos.close();
    156                     return new JarFile(tempJar, true, ZipFile.OPEN_READ | ZipFile.OPEN_DELETE);
    157                 } catch (IOException e) {
    158                     return null;
    159                 } finally {
    160                     if (fos != null) {
    161                         try {
    162                             fos.close();
    163                         } catch (IOException ex) {
    164                             return null;
    165                         }
    166                     }
    167                 }
    168             } finally {
    169                 if (is != null) {
    170                     is.close();
    171                 }
    172             }
    173         }
    174     }
    175 
    176     /**
    177      * Returns the JarEntry of the entry referenced by this {@code
    178      * URLConnection}.
    179      *
    180      * @return the JarEntry referenced
    181      *
    182      * @throws IOException
    183      *             if an IO error occurs while getting the entry
    184      */
    185     @Override
    186     public JarEntry getJarEntry() throws IOException {
    187         connect();
    188         return jarEntry;
    189 
    190     }
    191 
    192     /**
    193      * Look up the JarEntry of the entry referenced by this {@code
    194      * URLConnection}.
    195      */
    196     private void findJarEntry() throws IOException {
    197         if (getEntryName() == null) {
    198             return;
    199         }
    200         jarEntry = jarFile.getJarEntry(getEntryName());
    201         if (jarEntry == null) {
    202             throw new FileNotFoundException(getEntryName());
    203         }
    204     }
    205 
    206     /**
    207      * Creates an input stream for reading from this URL Connection.
    208      *
    209      * @return the input stream
    210      *
    211      * @throws IOException
    212      *             if an IO error occurs while connecting to the resource.
    213      */
    214     @Override
    215     public InputStream getInputStream() throws IOException {
    216         if (closed) {
    217             throw new IllegalStateException("JarURLConnection InputStream has been closed");
    218         }
    219         connect();
    220         if (jarInput != null) {
    221             return jarInput;
    222         }
    223         if (jarEntry == null) {
    224             throw new IOException("Jar entry not specified");
    225         }
    226         return jarInput = new JarURLConnectionInputStream(jarFile
    227                 .getInputStream(jarEntry), jarFile);
    228     }
    229 
    230     /**
    231      * Returns the content type of the resource. For jar file itself
    232      * "x-java/jar" should be returned, for jar entries the content type of the
    233      * entry should be returned. Returns non-null results ("content/unknown" for
    234      * unknown types).
    235      *
    236      * @return the content type
    237      */
    238     @Override
    239     public String getContentType() {
    240         if (url.getFile().endsWith("!/")) {
    241             // the type for jar file itself is always "x-java/jar"
    242             return "x-java/jar";
    243         }
    244         String cType = null;
    245         String entryName = getEntryName();
    246 
    247         if (entryName != null) {
    248             // if there is an Jar Entry, get the content type from the name
    249             cType = guessContentTypeFromName(entryName);
    250         } else {
    251             try {
    252                 connect();
    253                 cType = jarFileURLConnection.getContentType();
    254             } catch (IOException ioe) {
    255                 // Ignore
    256             }
    257         }
    258         if (cType == null) {
    259             cType = "content/unknown";
    260         }
    261         return cType;
    262     }
    263 
    264     /**
    265      * Returns the content length of the resource. Test cases reveal that if the
    266      * URL is referring to a Jar file, this method answers a content-length
    267      * returned by URLConnection. For jar entry it should return it's size.
    268      * Otherwise, it will return -1.
    269      *
    270      * @return the content length
    271      */
    272     @Override
    273     public int getContentLength() {
    274         try {
    275             connect();
    276             if (jarEntry == null) {
    277                 return jarFileURLConnection.getContentLength();
    278             }
    279             return (int) getJarEntry().getSize();
    280         } catch (IOException e) {
    281             // Ignored
    282         }
    283         return -1;
    284     }
    285 
    286     /**
    287      * Returns the object pointed by this {@code URL}. If this URLConnection is
    288      * pointing to a Jar File (no Jar Entry), this method will return a {@code
    289      * JarFile} If there is a Jar Entry, it will return the object corresponding
    290      * to the Jar entry content type.
    291      *
    292      * @return a non-null object
    293      *
    294      * @throws IOException
    295      *             if an IO error occurred
    296      *
    297      * @see ContentHandler
    298      * @see ContentHandlerFactory
    299      * @see java.io.IOException
    300      * @see #setContentHandlerFactory(ContentHandlerFactory)
    301      */
    302     @Override
    303     public Object getContent() throws IOException {
    304         connect();
    305         // if there is no Jar Entry, return a JarFile
    306         if (jarEntry == null) {
    307             return jarFile;
    308         }
    309         return super.getContent();
    310     }
    311 
    312     /**
    313      * Returns the permission, in this case the subclass, FilePermission object
    314      * which represents the permission necessary for this URLConnection to
    315      * establish the connection.
    316      *
    317      * @return the permission required for this URLConnection.
    318      *
    319      * @throws IOException
    320      *             thrown when an IO exception occurs while creating the
    321      *             permission.
    322      */
    323 
    324     @Override
    325     public Permission getPermission() throws IOException {
    326         return jarFileURLConnection.getPermission();
    327     }
    328 
    329     @Override
    330     public boolean getUseCaches() {
    331         return jarFileURLConnection.getUseCaches();
    332     }
    333 
    334     @Override
    335     public void setUseCaches(boolean usecaches) {
    336         jarFileURLConnection.setUseCaches(usecaches);
    337     }
    338 
    339     @Override
    340     public boolean getDefaultUseCaches() {
    341         return jarFileURLConnection.getDefaultUseCaches();
    342     }
    343 
    344     @Override
    345     public void setDefaultUseCaches(boolean defaultusecaches) {
    346         jarFileURLConnection.setDefaultUseCaches(defaultusecaches);
    347     }
    348 
    349     /**
    350      * Closes the cached files.
    351      */
    352     public static void closeCachedFiles() {
    353         Set<Map.Entry<URL, JarFile>> s = jarCache.entrySet();
    354         synchronized (jarCache) {
    355             Iterator<Map.Entry<URL, JarFile>> i = s.iterator();
    356             while (i.hasNext()) {
    357                 try {
    358                     ZipFile zip = i.next().getValue();
    359                     if (zip != null) {
    360                         zip.close();
    361                     }
    362                 } catch (IOException e) {
    363                     // Ignored
    364                 }
    365             }
    366         }
    367     }
    368 
    369     private class JarURLConnectionInputStream extends FilterInputStream {
    370         final JarFile jarFile;
    371 
    372         protected JarURLConnectionInputStream(InputStream in, JarFile file) {
    373             super(in);
    374             jarFile = file;
    375         }
    376 
    377         @Override
    378         public void close() throws IOException {
    379             super.close();
    380             if (!getUseCaches()) {
    381                 closed = true;
    382                 jarFile.close();
    383             }
    384         }
    385     }
    386 }
    387