All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.joshworks.snappy.loader.jar.JarURLConnection Maven / Gradle / Ivy

There is a newer version: 0.2
Show newest version
/*
 * Copyright 2012-2016 the original author or authors.
 *
 * Licensed 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 io.joshworks.snappy.loader.jar;

import io.joshworks.snappy.loader.util.AsciiBytes;

import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.FilePermission;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.security.Permission;
import java.util.jar.Manifest;

/**
 * {@link java.net.JarURLConnection} used to support {@link JarFile#getUrl()}.
 *
 * @author Phillip Webb
 * @author Andy Wilkinson
 */
class JarURLConnection extends java.net.JarURLConnection {

    private static final FileNotFoundException FILE_NOT_FOUND_EXCEPTION = new FileNotFoundException();

    private static final String SEPARATOR = "!/";

    private static final URL EMPTY_JAR_URL;
    private static final JarEntryName EMPTY_JAR_ENTRY_NAME = new JarEntryName("");
    private static final String FILE_COLON_DOUBLE_SLASH = "file://";
    private static final String READ_ACTION = "read";
    private static ThreadLocal useFastExceptions = new ThreadLocal();

    static {
        try {
            EMPTY_JAR_URL = new URL("jar:", null, 0, "file:!/", new URLStreamHandler() {
                @Override
                protected URLConnection openConnection(URL u) throws IOException {
                    // Stub URLStreamHandler to prevent the wrong JAR Handler from being
                    // Instantiated and cached.
                    return null;
                }
            });
        } catch (MalformedURLException ex) {
            throw new IllegalStateException(ex);
        }
    }

    private final JarFile jarFile;

    private Permission permission;

    private JarEntryData jarEntryData;

    private URL jarFileUrl;

    private JarEntryName jarEntryName;

    protected JarURLConnection(URL url, JarFile jarFile) throws IOException {
        // What we pass to super is ultimately ignored
        super(EMPTY_JAR_URL);
        this.url = url;
        String spec = getNormalizedFile(url)
                .substring(jarFile.getUrl().getFile().length());
        int separator;
        while ((separator = spec.indexOf(SEPARATOR)) > 0) {
            jarFile = getNestedJarFile(jarFile, spec.substring(0, separator));
            spec = spec.substring(separator + SEPARATOR.length());
        }
        this.jarFile = jarFile;
        this.jarEntryName = getJarEntryName(spec);
    }

    static void setUseFastExceptions(boolean useFastExceptions) {
        JarURLConnection.useFastExceptions.set(useFastExceptions);
    }

    private String getNormalizedFile(URL url) {
        if (!url.getFile().startsWith(FILE_COLON_DOUBLE_SLASH)) {
            return url.getFile();
        }
        return "file:" + url.getFile().substring(FILE_COLON_DOUBLE_SLASH.length());
    }

    private JarFile getNestedJarFile(JarFile jarFile, String name) throws IOException {
        JarEntry jarEntry = jarFile.getJarEntry(name);
        if (jarEntry == null) {
            throwFileNotFound(jarEntry, jarFile);
        }
        return jarFile.getNestedJarFile(jarEntry);
    }

    private JarEntryName getJarEntryName(String spec) {
        if (spec.length() == 0) {
            return EMPTY_JAR_ENTRY_NAME;
        }
        return new JarEntryName(spec);
    }

    @Override
    public void connect() throws IOException {
        if (!this.jarEntryName.isEmpty()) {
            this.jarEntryData = this.jarFile
                    .getJarEntryData(this.jarEntryName.asAsciiBytes());
            if (this.jarEntryData == null) {
                throwFileNotFound(this.jarEntryName, this.jarFile);
            }
        }
        this.connected = true;
    }

    private void throwFileNotFound(Object entry, JarFile jarFile)
            throws FileNotFoundException {
        if (Boolean.TRUE.equals(useFastExceptions.get())) {
            throw FILE_NOT_FOUND_EXCEPTION;
        }
        throw new FileNotFoundException(
                "JAR entry " + entry + " not found in " + jarFile.getName());
    }

    @Override
    public Manifest getManifest() throws IOException {
        try {
            return super.getManifest();
        } finally {
            this.connected = false;
        }
    }

    @Override
    public JarFile getJarFile() throws IOException {
        connect();
        return this.jarFile;
    }

    @Override
    public URL getJarFileURL() {
        if (this.jarFileUrl == null) {
            this.jarFileUrl = buildJarFileUrl();
        }
        return this.jarFileUrl;
    }

    private URL buildJarFileUrl() {
        try {
            String spec = this.jarFile.getUrl().getFile();
            if (spec.endsWith(SEPARATOR)) {
                spec = spec.substring(0, spec.length() - SEPARATOR.length());
            }
            if (spec.indexOf(SEPARATOR) == -1) {
                return new URL(spec);
            }
            return new URL("jar:" + spec);
        } catch (MalformedURLException ex) {
            throw new IllegalStateException(ex);
        }
    }

    @Override
    public JarEntry getJarEntry() throws IOException {
        connect();
        return (this.jarEntryData == null ? null : this.jarEntryData.asJarEntry());
    }

    @Override
    public String getEntryName() {
        return this.jarEntryName.toString();
    }

    @Override
    public InputStream getInputStream() throws IOException {
        connect();
        if (this.jarEntryName.isEmpty()) {
            throw new IOException("no entry name specified");
        }
        return this.jarEntryData.getInputStream();
    }

    @Override
    public int getContentLength() {
        try {
            connect();
            if (this.jarEntryData != null) {
                return this.jarEntryData.getSize();
            }
            return this.jarFile.size();
        } catch (IOException ex) {
            return -1;
        }
    }

    @Override
    public Object getContent() throws IOException {
        connect();
        return (this.jarEntryData == null ? this.jarFile : super.getContent());
    }

    @Override
    public String getContentType() {
        return this.jarEntryName.getContentType();
    }

    @Override
    public Permission getPermission() throws IOException {
        if (this.permission == null) {
            this.permission = new FilePermission(
                    this.jarFile.getRootJarFile().getFile().getPath(), READ_ACTION);
        }
        return this.permission;
    }

    /**
     * A JarEntryName parsed from a URL String.
     */
    private static class JarEntryName {

        private final AsciiBytes name;

        private String contentType;

        JarEntryName(String spec) {
            this.name = decode(spec);
        }

        private AsciiBytes decode(String source) {
            int length = (source == null ? 0 : source.length());
            if ((length == 0) || (source.indexOf('%') < 0)) {
                return new AsciiBytes(source);
            }
            ByteArrayOutputStream bos = new ByteArrayOutputStream(length);
            for (int i = 0; i < length; i++) {
                int ch = source.charAt(i);
                if (ch == '%') {
                    if ((i + 2) >= length) {
                        throw new IllegalArgumentException("Invalid encoded sequence \""
                                + source.substring(i) + "\"");
                    }
                    ch = decodeEscapeSequence(source, i);
                    i += 2;
                }
                bos.write(ch);
            }
            // AsciiBytes is what is used to store the JarEntries so make it symmetric
            return new AsciiBytes(bos.toByteArray());
        }

        private char decodeEscapeSequence(String source, int i) {
            int hi = Character.digit(source.charAt(i + 1), 16);
            int lo = Character.digit(source.charAt(i + 2), 16);
            if (hi == -1 || lo == -1) {
                throw new IllegalArgumentException(
                        "Invalid encoded sequence \"" + source.substring(i) + "\"");
            }
            return ((char) ((hi << 4) + lo));
        }

        @Override
        public String toString() {
            return this.name.toString();
        }

        public AsciiBytes asAsciiBytes() {
            return this.name;
        }

        public boolean isEmpty() {
            return this.name.length() == 0;
        }

        public String getContentType() {
            if (this.contentType == null) {
                this.contentType = deduceContentType();
            }
            return this.contentType;
        }

        private String deduceContentType() {
            // Guess the content type, don't bother with streams as mark is not supported
            String type = (isEmpty() ? "x-java/jar" : null);
            type = (type != null ? type : guessContentTypeFromName(toString()));
            type = (type != null ? type : "content/unknown");
            return type;
        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy