| /* |
| * Copyright 2014 Google Inc. |
| * |
| * 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 com.google.gwt.dev.url; |
| |
| import com.google.gwt.thirdparty.guava.common.collect.HashMultimap; |
| import com.google.gwt.thirdparty.guava.common.collect.Multimap; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.JarURLConnection; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.security.Permission; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Opens connections to Zip files when requested and gathers them in a list for later closing. |
| * <p> |
| * This closing functionality makes it possible to run untrusted plugin code (for example |
| * Generators) and still guarantee that there will be no accidental attempts to read from old and |
| * deleted Zip files using stale InputStreams. |
| */ |
| @SuppressWarnings("restriction") |
| public class CloseableJarHandler extends sun.net.www.protocol.jar.Handler { |
| |
| /** |
| * Passes through all URLConnection access but collects all created InputStreams. |
| */ |
| private class CloseableUrlConnection extends URLConnection { |
| |
| private JarURLConnection jarUrlConnection; |
| |
| public CloseableUrlConnection(URL jarUrl, JarURLConnection jarUrlConnection) { |
| super(jarUrl); |
| this.jarUrlConnection = jarUrlConnection; |
| } |
| |
| @Override |
| public void addRequestProperty(String key, String value) { |
| jarUrlConnection.addRequestProperty(key, value); |
| } |
| |
| @Override |
| public void connect() throws IOException { |
| jarUrlConnection.connect(); |
| } |
| |
| @Override |
| public boolean getAllowUserInteraction() { |
| return jarUrlConnection.getAllowUserInteraction(); |
| } |
| |
| @Override |
| public int getConnectTimeout() { |
| return jarUrlConnection.getConnectTimeout(); |
| } |
| |
| @Override |
| public Object getContent() throws IOException { |
| return jarUrlConnection.getContent(); |
| } |
| |
| @Override |
| public Object getContent(Class[] classes) throws IOException { |
| return jarUrlConnection.getContent(classes); |
| } |
| |
| @Override |
| public String getContentEncoding() { |
| return jarUrlConnection.getContentEncoding(); |
| } |
| |
| @Override |
| public int getContentLength() { |
| return jarUrlConnection.getContentLength(); |
| } |
| |
| // No @Override as the method is only available in Java 7. |
| public long getContentLengthLong() { |
| int contentLength = jarUrlConnection.getContentLength(); |
| if (contentLength == -1) { |
| throw new RuntimeException("Content length could not be read because it exceeded " |
| + Integer.MAX_VALUE + " bytes."); |
| } |
| return contentLength; |
| } |
| |
| @Override |
| public String getContentType() { |
| return jarUrlConnection.getContentType(); |
| } |
| |
| @Override |
| public long getDate() { |
| return jarUrlConnection.getDate(); |
| } |
| |
| @Override |
| public boolean getDefaultUseCaches() { |
| return jarUrlConnection.getDefaultUseCaches(); |
| } |
| |
| @Override |
| public boolean getDoInput() { |
| return jarUrlConnection.getDoInput(); |
| } |
| |
| @Override |
| public boolean getDoOutput() { |
| return jarUrlConnection.getDoOutput(); |
| } |
| |
| @Override |
| public long getExpiration() { |
| return jarUrlConnection.getExpiration(); |
| } |
| |
| @Override |
| public String getHeaderField(int n) { |
| return jarUrlConnection.getHeaderField(n); |
| } |
| |
| // No need to override getHeaderField<Blah> as they all get routed to get getHeaderField() |
| @Override |
| public String getHeaderField(String name) { |
| return jarUrlConnection.getHeaderField(name); |
| } |
| |
| @Override |
| public String getHeaderFieldKey(int n) { |
| return jarUrlConnection.getHeaderFieldKey(n); |
| } |
| |
| @Override |
| public Map<String, List<String>> getHeaderFields() { |
| return jarUrlConnection.getHeaderFields(); |
| } |
| |
| @Override |
| public long getIfModifiedSince() { |
| return jarUrlConnection.getIfModifiedSince(); |
| } |
| |
| @Override |
| public InputStream getInputStream() throws IOException { |
| InputStream inputStream = jarUrlConnection.getInputStream(); |
| URL jarFileURL = jarUrlConnection.getJarFileURL(); |
| inputStreamsByJarFilePath.put(jarFileURL.getFile(), inputStream); |
| return inputStream; |
| } |
| |
| @Override |
| public long getLastModified() { |
| return jarUrlConnection.getLastModified(); |
| } |
| |
| @Override |
| public OutputStream getOutputStream() throws IOException { |
| return jarUrlConnection.getOutputStream(); |
| } |
| |
| @Override |
| public Permission getPermission() throws IOException { |
| return jarUrlConnection.getPermission(); |
| } |
| |
| @Override |
| public int getReadTimeout() { |
| return jarUrlConnection.getReadTimeout(); |
| } |
| |
| @Override |
| public Map<String, List<String>> getRequestProperties() { |
| return jarUrlConnection.getRequestProperties(); |
| } |
| |
| @Override |
| public String getRequestProperty(String key) { |
| return jarUrlConnection.getRequestProperty(key); |
| } |
| |
| @Override |
| public URL getURL() { |
| return jarUrlConnection.getURL(); |
| } |
| |
| @Override |
| public boolean getUseCaches() { |
| return jarUrlConnection.getUseCaches(); |
| } |
| |
| @Override |
| public void setAllowUserInteraction(boolean allowuserinteraction) { |
| jarUrlConnection.setAllowUserInteraction(allowuserinteraction); |
| } |
| |
| @Override |
| public void setConnectTimeout(int timeout) { |
| jarUrlConnection.setConnectTimeout(timeout); |
| } |
| |
| @Override |
| public void setDefaultUseCaches(boolean defaultusecaches) { |
| jarUrlConnection.setDefaultUseCaches(defaultusecaches); |
| } |
| |
| @Override |
| public void setDoInput(boolean doinput) { |
| jarUrlConnection.setDoInput(doinput); |
| } |
| |
| @Override |
| public void setDoOutput(boolean dooutput) { |
| jarUrlConnection.setDoOutput(dooutput); |
| } |
| |
| @Override |
| public void setIfModifiedSince(long ifmodifiedsince) { |
| jarUrlConnection.setIfModifiedSince(ifmodifiedsince); |
| } |
| |
| @Override |
| public void setReadTimeout(int timeout) { |
| jarUrlConnection.setReadTimeout(timeout); |
| } |
| |
| @Override |
| public void setRequestProperty(String key, String value) { |
| jarUrlConnection.setRequestProperty(key, value); |
| } |
| |
| @Override |
| public void setUseCaches(boolean usecaches) { |
| jarUrlConnection.setUseCaches(usecaches); |
| } |
| |
| @Override |
| public String toString() { |
| return jarUrlConnection.toString(); |
| } |
| } |
| |
| private Multimap<String, InputStream> inputStreamsByJarFilePath = HashMultimap.create(); |
| |
| /** |
| * Closes all InputStreams that were created by the URL system that point at the given Jar file. |
| */ |
| public void closeStreams(String jarFilePath) throws IOException { |
| Collection<InputStream> inputStreams = inputStreamsByJarFilePath.get(jarFilePath); |
| if (inputStreams == null) { |
| return; |
| } |
| |
| for (InputStream inputStream : inputStreams) { |
| inputStream.close(); |
| } |
| inputStreamsByJarFilePath.removeAll(jarFilePath); |
| } |
| |
| @Override |
| protected URLConnection openConnection(URL jarUrl) throws IOException { |
| // Let the Jar system do the heavy lifting of opening the connection. |
| JarURLConnection jarUrlConnection = (JarURLConnection) super.openConnection(jarUrl); |
| // Ensures that when all connections have been closed the cached Zip file references will be |
| // cleared. |
| jarUrlConnection.setUseCaches(false); |
| |
| // Wrap the jar url connection in a way that will collect all created InputStreams so that they |
| // can be closed. |
| return new CloseableUrlConnection(jarUrl, jarUrlConnection); |
| } |
| } |