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

cn.taketoday.app.loader.jar.JarURLConnection Maven / Gradle / Ivy

There is a newer version: 5.0.0-Draft.1
Show newest version
/*
 * Copyright 2017 - 2023 the original author or authors.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see [http://www.gnu.org/licenses/]
 */

package cn.taketoday.app.loader.jar;

import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.net.URLStreamHandler;
import java.nio.charset.StandardCharsets;
import java.security.Permission;

/**
 * {@link java.net.JarURLConnection} used to support {@link JarFile#getUrl()}.
 *
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @author Rostyslav Dudka
 * @author Harry Yang
 * @since 4.0
 */
final class JarURLConnection extends java.net.JarURLConnection {

  private static final ThreadLocal useFastExceptions = new ThreadLocal<>();

  private static final FileNotFoundException FILE_NOT_FOUND_EXCEPTION
          = new FileNotFoundException("Jar file or entry not found");

  private static final IllegalStateException NOT_FOUND_CONNECTION_EXCEPTION
          = new IllegalStateException(FILE_NOT_FOUND_EXCEPTION);

  private static final String SEPARATOR = "!/";

  private static final URL EMPTY_JAR_URL;

  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 static final JarEntryName EMPTY_JAR_ENTRY_NAME = new JarEntryName(new StringSequence(""));

  private static final JarURLConnection NOT_FOUND_CONNECTION = JarURLConnection.notFound();

  private final AbstractJarFile jarFile;

  private Permission permission;

  private URL jarFileUrl;

  private final JarEntryName jarEntryName;

  private java.util.jar.JarEntry jarEntry;

  private JarURLConnection(URL url, AbstractJarFile jarFile, JarEntryName jarEntryName) throws IOException {
    // What we pass to super is ultimately ignored
    super(EMPTY_JAR_URL);
    this.url = url;
    this.jarFile = jarFile;
    this.jarEntryName = jarEntryName;
  }

  @Override
  public void connect() throws IOException {
    if (this.jarFile == null) {
      throw FILE_NOT_FOUND_EXCEPTION;
    }
    if (!this.jarEntryName.isEmpty() && this.jarEntry == null) {
      this.jarEntry = this.jarFile.getJarEntry(getEntryName());
      if (this.jarEntry == null) {
        throwFileNotFound(this.jarEntryName, this.jarFile);
      }
    }
    this.connected = true;
  }

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

  @Override
  public URL getJarFileURL() {
    if (this.jarFile == null) {
      throw NOT_FOUND_CONNECTION_EXCEPTION;
    }
    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.contains(SEPARATOR)) {
        return new URL(spec);
      }
      return new URL("jar:" + spec);
    }
    catch (MalformedURLException ex) {
      throw new IllegalStateException(ex);
    }
  }

  @Override
  public java.util.jar.JarEntry getJarEntry() throws IOException {
    if (this.jarEntryName == null || this.jarEntryName.isEmpty()) {
      return null;
    }
    connect();
    return this.jarEntry;
  }

  @Override
  public String getEntryName() {
    if (this.jarFile == null) {
      throw NOT_FOUND_CONNECTION_EXCEPTION;
    }
    return this.jarEntryName.toString();
  }

  @Override
  public InputStream getInputStream() throws IOException {
    if (this.jarFile == null) {
      throw FILE_NOT_FOUND_EXCEPTION;
    }
    if (this.jarEntryName.isEmpty() && this.jarFile.getType() == JarFile.JarFileType.DIRECT) {
      throw new IOException("no entry name specified");
    }
    connect();
    InputStream inputStream = (this.jarEntryName.isEmpty() ? this.jarFile.getInputStream()
                                                           : this.jarFile.getInputStream(this.jarEntry));
    if (inputStream == null) {
      throwFileNotFound(this.jarEntryName, this.jarFile);
    }
    return inputStream;
  }

  private void throwFileNotFound(Object entry, AbstractJarFile 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 int getContentLength() {
    long length = getContentLengthLong();
    if (length > Integer.MAX_VALUE) {
      return -1;
    }
    return (int) length;
  }

  @Override
  public long getContentLengthLong() {
    if (this.jarFile == null) {
      return -1;
    }
    try {
      if (this.jarEntryName.isEmpty()) {
        return this.jarFile.size();
      }
      java.util.jar.JarEntry entry = getJarEntry();
      return (entry != null) ? (int) entry.getSize() : -1;
    }
    catch (IOException ex) {
      return -1;
    }
  }

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

  @Override
  public String getContentType() {
    return (this.jarEntryName != null) ? this.jarEntryName.getContentType() : null;
  }

  @Override
  public Permission getPermission() throws IOException {
    if (this.jarFile == null) {
      throw FILE_NOT_FOUND_EXCEPTION;
    }
    if (this.permission == null) {
      this.permission = this.jarFile.getPermission();
    }
    return this.permission;
  }

  @Override
  public long getLastModified() {
    if (this.jarFile == null || this.jarEntryName.isEmpty()) {
      return 0;
    }
    try {
      java.util.jar.JarEntry entry = getJarEntry();
      return (entry != null) ? entry.getTime() : 0;
    }
    catch (IOException ex) {
      return 0;
    }
  }

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

  static JarURLConnection get(URL url, JarFile jarFile) throws IOException {
    StringSequence spec = new StringSequence(url.getFile());
    int index = indexOfRootSpec(spec, jarFile.getPathFromRoot());
    if (index == -1) {
      return (Boolean.TRUE.equals(useFastExceptions.get()) ? NOT_FOUND_CONNECTION
                                                           : new JarURLConnection(url, null, EMPTY_JAR_ENTRY_NAME));
    }
    int separator;
    while ((separator = spec.indexOf(SEPARATOR, index)) > 0) {
      JarEntryName entryName = JarEntryName.get(spec.subSequence(index, separator));
      JarEntry jarEntry = jarFile.getJarEntry(entryName.toCharSequence());
      if (jarEntry == null) {
        return JarURLConnection.notFound(jarFile, entryName);
      }
      jarFile = jarFile.getNestedJarFile(jarEntry);
      index = separator + SEPARATOR.length();
    }
    JarEntryName jarEntryName = JarEntryName.get(spec, index);
    if (Boolean.TRUE.equals(useFastExceptions.get()) && !jarEntryName.isEmpty()
            && !jarFile.containsEntry(jarEntryName.toString())) {
      return NOT_FOUND_CONNECTION;
    }
    return new JarURLConnection(url, jarFile.getWrapper(), jarEntryName);
  }

  private static int indexOfRootSpec(StringSequence file, String pathFromRoot) {
    int separatorIndex = file.indexOf(SEPARATOR);
    if (separatorIndex < 0 || !file.startsWith(pathFromRoot, separatorIndex)) {
      return -1;
    }
    return separatorIndex + SEPARATOR.length() + pathFromRoot.length();
  }

  private static JarURLConnection notFound() {
    try {
      return notFound(null, null);
    }
    catch (IOException ex) {
      throw new IllegalStateException(ex);
    }
  }

  private static JarURLConnection notFound(JarFile jarFile, JarEntryName jarEntryName) throws IOException {
    if (Boolean.TRUE.equals(useFastExceptions.get())) {
      return NOT_FOUND_CONNECTION;
    }
    return new JarURLConnection(null, jarFile, jarEntryName);
  }

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

    private final StringSequence name;

    private String contentType;

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

    private StringSequence decode(StringSequence source) {
      if (source.isEmpty() || (source.indexOf('%') < 0)) {
        return source;
      }
      ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length());
      write(source.toString(), bos);
      // AsciiBytes is what is used to store the JarEntries so make it symmetric
      return new StringSequence(AsciiBytes.toString(bos.toByteArray()));
    }

    private void write(String source, ByteArrayOutputStream outputStream) {
      int length = source.length();
      for (int i = 0; i < length; i++) {
        int c = source.charAt(i);
        if (c > 127) {
          String encoded = URLEncoder.encode(String.valueOf((char) c), StandardCharsets.UTF_8);
          write(encoded, outputStream);
        }
        else {
          if (c == '%') {
            if ((i + 2) >= length) {
              throw new IllegalArgumentException(
                      "Invalid encoded sequence \"" + source.substring(i) + "\"");
            }
            c = decodeEscapeSequence(source, i);
            i += 2;
          }
          outputStream.write(c);
        }
      }
    }

    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));
    }

    CharSequence toCharSequence() {
      return this.name;
    }

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

    boolean isEmpty() {
      return this.name.isEmpty();
    }

    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;
    }

    static JarEntryName get(StringSequence spec) {
      return get(spec, 0);
    }

    static JarEntryName get(StringSequence spec, int beginIndex) {
      if (spec.length() <= beginIndex) {
        return EMPTY_JAR_ENTRY_NAME;
      }
      return new JarEntryName(spec.subSequence(beginIndex));
    }

  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy