1/*
2 * Copyright (c) 1997, 2013, 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
26package sun.net.www.protocol.jar;
27
28import java.io.InputStream;
29import java.io.IOException;
30import java.io.FileNotFoundException;
31import java.io.BufferedInputStream;
32import java.net.URL;
33import java.net.URLConnection;
34import java.net.MalformedURLException;
35import java.net.UnknownServiceException;
36import java.util.Enumeration;
37import java.util.Map;
38import java.util.List;
39import java.util.jar.JarEntry;
40import java.util.jar.JarFile;
41import java.util.jar.Manifest;
42import java.security.Permission;
43
44/**
45 * @author Benjamin Renaud
46 * @since 1.2
47 */
48public class JarURLConnection extends java.net.JarURLConnection {
49
50    private static final boolean debug = false;
51
52    /* the Jar file factory. It handles both retrieval and caching.
53     */
54    private static final JarFileFactory factory = JarFileFactory.getInstance();
55
56    /* the url for the Jar file */
57    private URL jarFileURL;
58
59    /* the permission to get this JAR file. This is the actual, ultimate,
60     * permission, returned by the jar file factory.
61     */
62    private Permission permission;
63
64    /* the url connection for the JAR file */
65    private URLConnection jarFileURLConnection;
66
67    /* the entry name, if any */
68    private String entryName;
69
70    /* the JarEntry */
71    private JarEntry jarEntry;
72
73    /* the jar file corresponding to this connection */
74    private JarFile jarFile;
75
76    /* the content type for this connection */
77    private String contentType;
78
79    public JarURLConnection(URL url, Handler handler)
80    throws MalformedURLException, IOException {
81        super(url);
82
83        jarFileURL = getJarFileURL();
84        jarFileURLConnection = jarFileURL.openConnection();
85        // whether, or not, the embedded URL should use the cache will depend
86        // on this instance's cache value
87        jarFileURLConnection.setUseCaches(useCaches);
88        entryName = getEntryName();
89    }
90
91    public JarFile getJarFile() throws IOException {
92        connect();
93        return jarFile;
94    }
95
96    public JarEntry getJarEntry() throws IOException {
97        connect();
98        return jarEntry;
99    }
100
101    public Permission getPermission() throws IOException {
102        return jarFileURLConnection.getPermission();
103    }
104
105    class JarURLInputStream extends java.io.FilterInputStream {
106        JarURLInputStream (InputStream src) {
107            super (src);
108        }
109        public void close () throws IOException {
110            try {
111                super.close();
112            } finally {
113                if (!getUseCaches()) {
114                    jarFile.close();
115                }
116            }
117        }
118    }
119
120
121
122    public void connect() throws IOException {
123        if (!connected) {
124            /* the factory call will do the security checks */
125            jarFile = factory.get(getJarFileURL(), getUseCaches());
126
127            /* we also ask the factory the permission that was required
128             * to get the jarFile, and set it as our permission.
129             */
130            if (getUseCaches()) {
131                boolean oldUseCaches = jarFileURLConnection.getUseCaches();
132                jarFileURLConnection = factory.getConnection(jarFile);
133                jarFileURLConnection.setUseCaches(oldUseCaches);
134            }
135
136            if ((entryName != null)) {
137                jarEntry = (JarEntry)jarFile.getEntry(entryName);
138                if (jarEntry == null) {
139                    try {
140                        if (!getUseCaches()) {
141                            jarFile.close();
142                        }
143                    } catch (Exception e) {
144                    }
145                    throw new FileNotFoundException("JAR entry " + entryName +
146                                                    " not found in " +
147                                                    jarFile.getName());
148                }
149            }
150            connected = true;
151        }
152    }
153
154    public InputStream getInputStream() throws IOException {
155        connect();
156
157        InputStream result = null;
158
159        if (entryName == null) {
160            throw new IOException("no entry name specified");
161        } else {
162            if (jarEntry == null) {
163                throw new FileNotFoundException("JAR entry " + entryName +
164                                                " not found in " +
165                                                jarFile.getName());
166            }
167            result = new JarURLInputStream (jarFile.getInputStream(jarEntry));
168        }
169        return result;
170    }
171
172    public int getContentLength() {
173        long result = getContentLengthLong();
174        if (result > Integer.MAX_VALUE)
175            return -1;
176        return (int) result;
177    }
178
179    public long getContentLengthLong() {
180        long result = -1;
181        try {
182            connect();
183            if (jarEntry == null) {
184                /* if the URL referes to an archive */
185                result = jarFileURLConnection.getContentLengthLong();
186            } else {
187                /* if the URL referes to an archive entry */
188                result = getJarEntry().getSize();
189            }
190        } catch (IOException e) {
191        }
192        return result;
193    }
194
195    public Object getContent() throws IOException {
196        Object result = null;
197
198        connect();
199        if (entryName == null) {
200            result = jarFile;
201        } else {
202            result = super.getContent();
203        }
204        return result;
205    }
206
207    public String getContentType() {
208        if (contentType == null) {
209            if (entryName == null) {
210                contentType = "x-java/jar";
211            } else {
212                try {
213                    connect();
214                    InputStream in = jarFile.getInputStream(jarEntry);
215                    contentType = guessContentTypeFromStream(
216                                        new BufferedInputStream(in));
217                    in.close();
218                } catch (IOException e) {
219                    // don't do anything
220                }
221            }
222            if (contentType == null) {
223                contentType = guessContentTypeFromName(entryName);
224            }
225            if (contentType == null) {
226                contentType = "content/unknown";
227            }
228        }
229        return contentType;
230    }
231
232    public String getHeaderField(String name) {
233        return jarFileURLConnection.getHeaderField(name);
234    }
235
236    /**
237     * Sets the general request property.
238     *
239     * @param   key     the keyword by which the request is known
240     *                  (e.g., "<code>accept</code>").
241     * @param   value   the value associated with it.
242     */
243    public void setRequestProperty(String key, String value) {
244        jarFileURLConnection.setRequestProperty(key, value);
245    }
246
247    /**
248     * Returns the value of the named general request property for this
249     * connection.
250     *
251     * @return  the value of the named general request property for this
252     *           connection.
253     */
254    public String getRequestProperty(String key) {
255        return jarFileURLConnection.getRequestProperty(key);
256    }
257
258    /**
259     * Adds a general request property specified by a
260     * key-value pair.  This method will not overwrite
261     * existing values associated with the same key.
262     *
263     * @param   key     the keyword by which the request is known
264     *                  (e.g., "<code>accept</code>").
265     * @param   value   the value associated with it.
266     */
267    public void addRequestProperty(String key, String value) {
268        jarFileURLConnection.addRequestProperty(key, value);
269    }
270
271    /**
272     * Returns an unmodifiable Map of general request
273     * properties for this connection. The Map keys
274     * are Strings that represent the request-header
275     * field names. Each Map value is a unmodifiable List
276     * of Strings that represents the corresponding
277     * field values.
278     *
279     * @return  a Map of the general request properties for this connection.
280     */
281    public Map<String,List<String>> getRequestProperties() {
282        return jarFileURLConnection.getRequestProperties();
283    }
284
285    /**
286     * Set the value of the <code>allowUserInteraction</code> field of
287     * this <code>URLConnection</code>.
288     *
289     * @param   allowuserinteraction   the new value.
290     * @see     java.net.URLConnection#allowUserInteraction
291     */
292    public void setAllowUserInteraction(boolean allowuserinteraction) {
293        jarFileURLConnection.setAllowUserInteraction(allowuserinteraction);
294    }
295
296    /**
297     * Returns the value of the <code>allowUserInteraction</code> field for
298     * this object.
299     *
300     * @return  the value of the <code>allowUserInteraction</code> field for
301     *          this object.
302     * @see     java.net.URLConnection#allowUserInteraction
303     */
304    public boolean getAllowUserInteraction() {
305        return jarFileURLConnection.getAllowUserInteraction();
306    }
307
308    /*
309     * cache control
310     */
311
312    /**
313     * Sets the value of the <code>useCaches</code> field of this
314     * <code>URLConnection</code> to the specified value.
315     * <p>
316     * Some protocols do caching of documents.  Occasionally, it is important
317     * to be able to "tunnel through" and ignore the caches (e.g., the
318     * "reload" button in a browser).  If the UseCaches flag on a connection
319     * is true, the connection is allowed to use whatever caches it can.
320     *  If false, caches are to be ignored.
321     *  The default value comes from DefaultUseCaches, which defaults to
322     * true.
323     *
324     * @see     java.net.URLConnection#useCaches
325     */
326    public void setUseCaches(boolean usecaches) {
327        jarFileURLConnection.setUseCaches(usecaches);
328    }
329
330    /**
331     * Returns the value of this <code>URLConnection</code>'s
332     * <code>useCaches</code> field.
333     *
334     * @return  the value of this <code>URLConnection</code>'s
335     *          <code>useCaches</code> field.
336     * @see     java.net.URLConnection#useCaches
337     */
338    public boolean getUseCaches() {
339        return jarFileURLConnection.getUseCaches();
340    }
341
342    /**
343     * Sets the value of the <code>ifModifiedSince</code> field of
344     * this <code>URLConnection</code> to the specified value.
345     *
346     * @param   ifmodifiedsince   the new value.
347     * @see     java.net.URLConnection#ifModifiedSince
348     */
349    public void setIfModifiedSince(long ifmodifiedsince) {
350        jarFileURLConnection.setIfModifiedSince(ifmodifiedsince);
351    }
352
353   /**
354     * Sets the default value of the <code>useCaches</code> field to the
355     * specified value.
356     *
357     * @param   defaultusecaches   the new value.
358     * @see     java.net.URLConnection#useCaches
359     */
360    public void setDefaultUseCaches(boolean defaultusecaches) {
361        jarFileURLConnection.setDefaultUseCaches(defaultusecaches);
362    }
363
364   /**
365     * Returns the default value of a <code>URLConnection</code>'s
366     * <code>useCaches</code> flag.
367     * <p>
368     * Ths default is "sticky", being a part of the static state of all
369     * URLConnections.  This flag applies to the next, and all following
370     * URLConnections that are created.
371     *
372     * @return  the default value of a <code>URLConnection</code>'s
373     *          <code>useCaches</code> flag.
374     * @see     java.net.URLConnection#useCaches
375     */
376    public boolean getDefaultUseCaches() {
377        return jarFileURLConnection.getDefaultUseCaches();
378    }
379}
380