/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java.net; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.cert.Certificate; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import libcore.net.UriCodec; /** * This class establishes a connection to a {@code jar:} URL using the {@code * JAR} protocol. A {@code JarURLConnection} instance can refer to either a JAR * archive file or to an entry of such a file. {@code jar:} URLs are specified * as follows: <i>jar:{archive-url}!/{entry}</i> where "!/" is called a * separator. This separator is important to determine if an archive or an entry * of an archive is referred. * <p> * Examples: * <li>Archive: {@code jar:http://www.example.com/applets/archive.jar!/}</li> * <li>File Entry: {@code * jar:http://www.example.com/applets/archive.jar!/test.class}</li> * <li>Directory Entry: {@code * jar:http://www.example.com/applets/archive.jar!/applets/}</li> */ public abstract class JarURLConnection extends URLConnection { /** * The location part of the represented URL. */ protected URLConnection jarFileURLConnection; private String entryName; private URL fileURL; // the file component of the URL private String file; /** * Constructs an instance of {@code JarURLConnection} that refers to the * specified URL. * * @param url * the URL that contains the location to connect to. * @throws MalformedURLException * if an invalid URL has been entered. */ protected JarURLConnection(URL url) throws MalformedURLException { super(url); file = decode(url.getFile()); int sepIdx; if ((sepIdx = file.indexOf("!/")) < 0) { throw new MalformedURLException(); } fileURL = new URL(file.substring(0, sepIdx)); sepIdx += 2; if (file.length() == sepIdx) { return; } entryName = file.substring(sepIdx, file.length()); if (url.getRef() != null) { entryName += "#" + url.getRef(); } } /** * Returns all attributes of the {@code JarEntry} referenced by this {@code * JarURLConnection}. * * @return the attributes of the referenced {@code JarEntry}. * @throws IOException * if an I/O exception occurs while retrieving the * JAR-entries. */ public Attributes getAttributes() throws java.io.IOException { JarEntry jEntry = getJarEntry(); return (jEntry == null) ? null : jEntry.getAttributes(); } /** * Returns all certificates of the {@code JarEntry} referenced by this * {@code JarURLConnection} instance. This method will return {@code null} * until the {@code InputStream} has been completely verified. * * @return the certificates of the {@code JarEntry} as an array. * @throws IOException * if there is an I/O exception occurs while getting the * {@code JarEntry}. */ public Certificate[] getCertificates() throws java.io.IOException { JarEntry jEntry = getJarEntry(); if (jEntry == null) { return null; } return jEntry.getCertificates(); } /** * Gets the name of the entry referenced by this {@code JarURLConnection}. * The return value will be {@code null} if this instance refers to a JAR * file rather than an JAR file entry. * * @return the {@code JarEntry} name this instance refers to. */ public String getEntryName() { return entryName; } /** * Gets the {@code JarEntry} object of the entry referenced by this {@code * JarURLConnection}. * * @return the referenced {@code JarEntry} object or {@code null} if no * entry name is specified. * @throws IOException * if an error occurs while getting the file or file-entry. */ public JarEntry getJarEntry() throws IOException { if (!connected) { connect(); } if (entryName == null) { return null; } // The entry must exist since the connect succeeded return getJarFile().getJarEntry(entryName); } /** * Gets the manifest file associated with this JAR-URL. * * @return the manifest of the referenced JAR-file. * @throws IOException * if an error occurs while getting the manifest file. */ public Manifest getManifest() throws java.io.IOException { return (Manifest)getJarFile().getManifest().clone(); } /** * Gets the {@code JarFile} object referenced by this {@code * JarURLConnection}. * * @return the referenced JarFile object. * @throws IOException * if an I/O exception occurs while retrieving the JAR-file. */ public abstract JarFile getJarFile() throws java.io.IOException; /** * Gets the URL to the JAR-file referenced by this {@code JarURLConnection}. * * @return the URL to the JAR-file or {@code null} if there was an error * retrieving the URL. */ public URL getJarFileURL() { return fileURL; } /** * Gets all attributes of the manifest file referenced by this {@code * JarURLConnection}. If this instance refers to a JAR-file rather than a * JAR-file entry, {@code null} will be returned. * * @return the attributes of the manifest file or {@code null}. * @throws IOException * if an I/O exception occurs while retrieving the {@code * JarFile}. */ public Attributes getMainAttributes() throws java.io.IOException { Manifest m = getJarFile().getManifest(); return (m == null) ? null : m.getMainAttributes(); } private static String decode(String encoded) throws MalformedURLException { try { // "+" means "+" in URLs. i.e. like RFC 3986, not like // MIME application/x-www-form-urlencoded final boolean convertPlus = false; return UriCodec.decode( encoded, convertPlus, StandardCharsets.UTF_8, true /* throwOnFailure */); } catch (IllegalArgumentException e) { throw new MalformedURLException("Unable to decode URL", e); } } }