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
18package java.net;
19
20import java.io.IOException;
21import java.nio.charset.StandardCharsets;
22import java.security.cert.Certificate;
23import java.util.jar.Attributes;
24import java.util.jar.JarEntry;
25import java.util.jar.JarFile;
26import java.util.jar.Manifest;
27import libcore.net.UriCodec;
28
29/**
30 * This class establishes a connection to a {@code jar:} URL using the {@code
31 * JAR} protocol. A {@code JarURLConnection} instance can refer to either a JAR
32 * archive file or to an entry of such a file. {@code jar:} URLs are specified
33 * as follows: <i>jar:{archive-url}!/{entry}</i> where "!/" is called a
34 * separator. This separator is important to determine if an archive or an entry
35 * of an archive is referred.
36 * <p>
37 * Examples:
38 * <li>Archive: {@code jar:http://www.example.com/applets/archive.jar!/}</li>
39 * <li>File Entry: {@code
40 * jar:http://www.example.com/applets/archive.jar!/test.class}</li>
41 * <li>Directory Entry: {@code
42 * jar:http://www.example.com/applets/archive.jar!/applets/}</li>
43 */
44public abstract class JarURLConnection extends URLConnection {
45
46    /**
47     * The location part of the represented URL.
48     */
49    protected URLConnection jarFileURLConnection;
50
51    private String entryName;
52
53    private URL fileURL;
54
55    // the file component of the URL
56    private String file;
57
58    /**
59     * Constructs an instance of {@code JarURLConnection} that refers to the
60     * specified URL.
61     *
62     * @param url
63     *            the URL that contains the location to connect to.
64     * @throws MalformedURLException
65     *             if an invalid URL has been entered.
66     */
67    protected JarURLConnection(URL url) throws MalformedURLException {
68        super(url);
69        file = decode(url.getFile());
70
71        int sepIdx;
72        if ((sepIdx = file.indexOf("!/")) < 0) {
73            throw new MalformedURLException();
74        }
75        fileURL = new URL(file.substring(0, sepIdx));
76        sepIdx += 2;
77        if (file.length() == sepIdx) {
78            return;
79        }
80        entryName = file.substring(sepIdx, file.length());
81        if (url.getRef() != null) {
82            entryName += "#" + url.getRef();
83        }
84    }
85
86    /**
87     * Returns all attributes of the {@code JarEntry} referenced by this {@code
88     * JarURLConnection}.
89     *
90     * @return the attributes of the referenced {@code JarEntry}.
91     * @throws IOException
92     *                if an I/O exception occurs while retrieving the
93     *                JAR-entries.
94     */
95    public Attributes getAttributes() throws java.io.IOException {
96        JarEntry jEntry = getJarEntry();
97        return (jEntry == null) ? null : jEntry.getAttributes();
98    }
99
100    /**
101     * Returns all certificates of the {@code JarEntry} referenced by this
102     * {@code JarURLConnection} instance. This method will return {@code null}
103     * until the {@code InputStream} has been completely verified.
104     *
105     * @return the certificates of the {@code JarEntry} as an array.
106     * @throws IOException
107     *                if there is an I/O exception occurs while getting the
108     *                {@code JarEntry}.
109     */
110    public Certificate[] getCertificates() throws java.io.IOException {
111        JarEntry jEntry = getJarEntry();
112        if (jEntry == null) {
113            return null;
114        }
115
116        return jEntry.getCertificates();
117    }
118
119    /**
120     * Gets the name of the entry referenced by this {@code JarURLConnection}.
121     * The return value will be {@code null} if this instance refers to a JAR
122     * file rather than an JAR file entry.
123     *
124     * @return the {@code JarEntry} name this instance refers to.
125     */
126    public String getEntryName() {
127        return entryName;
128    }
129
130    /**
131     * Gets the {@code JarEntry} object of the entry referenced by this {@code
132     * JarURLConnection}.
133     *
134     * @return the referenced {@code JarEntry} object or {@code null} if no
135     *         entry name is specified.
136     * @throws IOException
137     *             if an error occurs while getting the file or file-entry.
138     */
139    public JarEntry getJarEntry() throws IOException {
140        if (!connected) {
141            connect();
142        }
143        if (entryName == null) {
144            return null;
145        }
146        // The entry must exist since the connect succeeded
147        return getJarFile().getJarEntry(entryName);
148    }
149
150    /**
151     * Gets the manifest file associated with this JAR-URL.
152     *
153     * @return the manifest of the referenced JAR-file.
154     * @throws IOException
155     *             if an error occurs while getting the manifest file.
156     */
157    public Manifest getManifest() throws java.io.IOException {
158        return (Manifest)getJarFile().getManifest().clone();
159    }
160
161    /**
162     * Gets the {@code JarFile} object referenced by this {@code
163     * JarURLConnection}.
164     *
165     * @return the referenced JarFile object.
166     * @throws IOException
167     *                if an I/O exception occurs while retrieving the JAR-file.
168     */
169    public abstract JarFile getJarFile() throws java.io.IOException;
170
171    /**
172     * Gets the URL to the JAR-file referenced by this {@code JarURLConnection}.
173     *
174     * @return the URL to the JAR-file or {@code null} if there was an error
175     *         retrieving the URL.
176     */
177    public URL getJarFileURL() {
178        return fileURL;
179    }
180
181    /**
182     * Gets all attributes of the manifest file referenced by this {@code
183     * JarURLConnection}. If this instance refers to a JAR-file rather than a
184     * JAR-file entry, {@code null} will be returned.
185     *
186     * @return the attributes of the manifest file or {@code null}.
187     * @throws IOException
188     *                if an I/O exception occurs while retrieving the {@code
189     *                JarFile}.
190     */
191    public Attributes getMainAttributes() throws java.io.IOException {
192        Manifest m = getJarFile().getManifest();
193        return (m == null) ? null : m.getMainAttributes();
194    }
195
196    private static String decode(String encoded) throws MalformedURLException {
197        try {
198            // "+" means "+" in URLs. i.e. like RFC 3986, not like
199            // MIME application/x-www-form-urlencoded
200            final boolean convertPlus = false;
201            return UriCodec.decode(
202                    encoded, convertPlus, StandardCharsets.UTF_8, true /* throwOnFailure */);
203        } catch (IllegalArgumentException e) {
204            throw new MalformedURLException("Unable to decode URL", e);
205        }
206    }
207
208}
209