From 59d2d03b074c70cc9c771d4787353726b1426d91 Mon Sep 17 00:00:00 2001 From: Adam Domurad Date: Thu, 14 Feb 2013 15:48:21 -0500 Subject: Fix PR835, use HEAD requests to query if resource URL is valid --- .../sourceforge/jnlp/cache/ResourceTracker.java | 83 +++++++++++++++++----- netx/net/sourceforge/jnlp/util/StreamUtils.java | 58 +++++++++++++++ 2 files changed, 124 insertions(+), 17 deletions(-) create mode 100644 netx/net/sourceforge/jnlp/util/StreamUtils.java (limited to 'netx') diff --git a/netx/net/sourceforge/jnlp/cache/ResourceTracker.java b/netx/net/sourceforge/jnlp/cache/ResourceTracker.java index 849bafa..898594f 100644 --- a/netx/net/sourceforge/jnlp/cache/ResourceTracker.java +++ b/netx/net/sourceforge/jnlp/cache/ResourceTracker.java @@ -35,7 +35,9 @@ import java.net.URLDecoder; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.jar.JarOutputStream; import java.util.jar.Pack200; @@ -47,6 +49,7 @@ import net.sourceforge.jnlp.Version; import net.sourceforge.jnlp.event.DownloadEvent; import net.sourceforge.jnlp.event.DownloadListener; import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.util.StreamUtils; import net.sourceforge.jnlp.util.WeakList; /** @@ -567,7 +570,7 @@ public class ResourceTracker { if (threads < maxThreads) { threads++; - Thread thread = new Thread(new Downloader()); + Thread thread = new Thread(new Downloader(), "DownloaderThread" + threads); thread.start(); } } @@ -793,6 +796,12 @@ public class ResourceTracker { // connect URL finalLocation = findBestUrl(resource); + + if (finalLocation == null) { + System.err.println("Attempted to download " + resource.location + ", but failed to connect!"); + throw new NullPointerException("finalLocation == null"); // Caught below + } + resource.setDownloadLocation(finalLocation); URLConnection connection = finalLocation.openConnection(); // this won't change so should be okay unsynchronized connection.addRequestProperty("Accept-Encoding", "pack200-gzip, gzip"); @@ -851,12 +860,44 @@ public class ResourceTracker { entry.unlock(); } } + /** + * Connects to the given URL, and grabs a response code if the URL uses + * the HTTP protocol, or returns an arbitrary valid HTTP response code. + * + * @return the response code if HTTP connection, or HttpURLConnection.HTTP_OK if not. + * @throws IOException + */ + private static int getUrlResponseCode(URL url, Map requestProperties, String requestMethod) throws IOException { + URLConnection connection = url.openConnection(); + + for (Map.Entry property : requestProperties.entrySet()){ + connection.addRequestProperty(property.getKey(), property.getValue()); + } + + if (connection instanceof HttpURLConnection) { + HttpURLConnection httpConnection = (HttpURLConnection)connection; + httpConnection.setRequestMethod(requestMethod); + + int responseCode = httpConnection.getResponseCode(); + + /* Fully consuming current request helps with connection re-use + * See http://docs.oracle.com/javase/1.5.0/docs/guide/net/http-keepalive.html */ + StreamUtils.consumeAndCloseInputStream(httpConnection.getInputStream()); + + return responseCode; + } + + return HttpURLConnection.HTTP_OK /* return a valid response code */; + } + /** - * Returns the best URL to use for downloading the resource + * Returns the 'best' valid URL for the given resource. + * This first adjusts the file name to take into account file versioning + * and packing, if possible. * * @param resource the resource - * @return a URL or null + * @return the best URL, or null if all failed to resolve */ private URL findBestUrl(Resource resource) { DownloadOptions options = downloadOptions.get(resource); @@ -869,29 +910,37 @@ public class ResourceTracker { System.err.println("All possible urls for " + resource.toString() + " : " + urls); } - URL bestUrl = null; + for (URL url : urls) { try { - URLConnection connection = url.openConnection(); - connection.addRequestProperty("Accept-Encoding", "pack200-gzip, gzip"); - if (connection instanceof HttpURLConnection) { - HttpURLConnection con = (HttpURLConnection)connection; - int responseCode = con.getResponseCode(); - if (responseCode == -1 || responseCode < 200 || responseCode >= 300) { - continue; + Map requestProperties = new HashMap(); + requestProperties.put("Accept-Encoding", "pack200-gzip, gzip"); + + int responseCode = getUrlResponseCode(url, requestProperties, "HEAD"); + + if (responseCode == HttpURLConnection.HTTP_NOT_IMPLEMENTED ) { + System.err.println("NOTE: The server does not appear to support HEAD requests, falling back to GET requests."); + /* Fallback: use GET request in the rare case the server does not support HEAD requests */ + responseCode = getUrlResponseCode(url, requestProperties, "GET"); + } + + /* Check if within valid response code range */ + if (responseCode >= 200 && responseCode < 300) { + if (JNLPRuntime.isDebug()) { + System.err.println("best url for " + resource.toString() + " is " + url.toString()); } + return url; /* This is the best URL */ } + } catch (IOException e) { + // continue to next candidate if (JNLPRuntime.isDebug()) { - System.err.println("best url for " + resource.toString() + " is " + url.toString()); + System.err.println("While processing " + url.toString() + " for resource " + resource.toString() + " got " + e); } - bestUrl = url; - break; - } catch (IOException e) { - // continue } } - return bestUrl; + /* No valid URL, return null */ + return null; } /** diff --git a/netx/net/sourceforge/jnlp/util/StreamUtils.java b/netx/net/sourceforge/jnlp/util/StreamUtils.java new file mode 100644 index 0000000..ed92fb6 --- /dev/null +++ b/netx/net/sourceforge/jnlp/util/StreamUtils.java @@ -0,0 +1,58 @@ +/* +Copyright (C) 2013 Red Hat, Inc. + +This file is part of IcedTea. + +IcedTea 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, version 2. + +IcedTea 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 IcedTea; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. + */ + +package net.sourceforge.jnlp.util; + +import java.io.IOException; +import java.io.InputStream; + +public class StreamUtils { + + /** + * Ensure a stream is fully read, required for correct behaviour in some + * APIs, namely HttpURLConnection. + * @throws IOException + */ + public static void consumeAndCloseInputStream(InputStream in) throws IOException { + byte[] throwAwayBuffer = new byte[256]; + while (in.read(throwAwayBuffer) > 0) { + /* ignore contents */ + } + in.close(); + } + +} \ No newline at end of file -- cgit v1.2.3