Proxy handling now detects need for updated credentials.
This revamps the way proxy handling is done to allow us to just proceed as normal, and then if we get HTTP errors that indicate that we need a proxy or need proxy credentials, we ask for them and then retry everything. We still try to auto-detect the need for a proxy on our very first invocation because on the first invocation, a failure to fetch a URL may well indicate that a proxy is needed, but that same assumption does not hold on subsequent invocations. In those later cases, it's probably just a transient network failure and it would be weird and annoying to pop up the "please provide proxy config" dialog in those cases. Thanks pb00068 for getting this ball rolling, even though once again I have nearly entirely rewritten the PR. I need to kick that habit. :)
This commit is contained in:
@@ -10,6 +10,14 @@
|
||||
* Added support for [Proxy Auto-config](https://en.wikipedia.org/wiki/Proxy_auto-config) via PAC
|
||||
files.
|
||||
|
||||
* Proxy handling can now recover from credentials going out of date. It will detect the error and
|
||||
ask for updated credentials.
|
||||
|
||||
* Added `try_no_proxy` system property. This instructs Getdown to always first try to run without a
|
||||
proxy, regardless of whether it has been configured to use a proxy in the past. And if it can run
|
||||
without a proxy, it does so for that session, but retains the proxy config for future sessions in
|
||||
which the proxy may again be needed.
|
||||
|
||||
## 1.8.4 - May 14, 2019
|
||||
|
||||
* Added `verify_timeout` config to allow customization of the default (60 second) timeout during
|
||||
|
||||
@@ -7,7 +7,6 @@ package com.threerings.getdown.data;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
@@ -23,8 +22,8 @@ import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import com.threerings.getdown.net.Connector;
|
||||
import com.threerings.getdown.util.*;
|
||||
// avoid ambiguity with java.util.Base64 which we can't use as it's 1.8+
|
||||
import com.threerings.getdown.util.Base64;
|
||||
@@ -271,10 +270,10 @@ public class Application
|
||||
return config;
|
||||
}
|
||||
|
||||
/** The proxy that should be used to do HTTP downloads. This must be configured prior to using
|
||||
* the application instance. Yes this is a public mutable field, no I'm not going to create a
|
||||
/** A helper that is used to do HTTP downloads. This must be configured prior to using the
|
||||
* application instance. Yes this is a public mutable field, no I'm not going to create a
|
||||
* getter and setter just to pretend like that's not the case. */
|
||||
public Proxy proxy = Proxy.NO_PROXY;
|
||||
public Connector conn = Connector.DEFAULT;
|
||||
|
||||
/**
|
||||
* Creates an application instance which records the location of the {@code getdown.txt}
|
||||
@@ -989,16 +988,8 @@ public class Application
|
||||
args.add("-Xdock:name=" + _dockName);
|
||||
}
|
||||
|
||||
// pass along our proxy settings
|
||||
if (proxy.type() == Proxy.Type.HTTP && proxy.address() instanceof InetSocketAddress) {
|
||||
InetSocketAddress proxyAddr = (InetSocketAddress) proxy.address();
|
||||
String proxyHost = proxyAddr.getHostString();
|
||||
int proxyPort = proxyAddr.getPort();
|
||||
args.add("-Dhttp.proxyHost=" + proxyHost);
|
||||
args.add("-Dhttp.proxyPort=" + proxyPort);
|
||||
args.add("-Dhttps.proxyHost=" + proxyHost);
|
||||
args.add("-Dhttps.proxyPort=" + proxyPort);
|
||||
}
|
||||
// forward our proxy settings
|
||||
conn.addProxyArgs(args);
|
||||
|
||||
// add the marker indicating the app is running in getdown
|
||||
args.add("-D" + Properties.GETDOWN + "=true");
|
||||
@@ -1258,10 +1249,10 @@ public class Application
|
||||
}
|
||||
|
||||
if (_latest != null) {
|
||||
try (InputStream in = ConnectionUtil.open(proxy, _latest, 0, 0).getInputStream();
|
||||
InputStreamReader reader = new InputStreamReader(in, UTF_8);
|
||||
BufferedReader bin = new BufferedReader(reader)) {
|
||||
for (String[] pair : Config.parsePairs(bin, Config.createOpts(false))) {
|
||||
try {
|
||||
List<String[]> vdata = Config.parsePairs(
|
||||
new StringReader(conn.fetch(_latest)), Config.createOpts(false));
|
||||
for (String[] pair : vdata) {
|
||||
if ("version".equals(pair[0])) {
|
||||
_targetVersion = Math.max(Long.parseLong(pair[1]), _targetVersion);
|
||||
if (fileVersion != -1 && _targetVersion > fileVersion) {
|
||||
@@ -1485,8 +1476,7 @@ public class Application
|
||||
/**
|
||||
* Downloads a new copy of CONFIG_FILE.
|
||||
*/
|
||||
protected void downloadConfigFile ()
|
||||
throws IOException
|
||||
protected void downloadConfigFile () throws IOException
|
||||
{
|
||||
downloadControlFile(CONFIG_FILE, 0);
|
||||
}
|
||||
@@ -1628,8 +1618,7 @@ public class Application
|
||||
* Download a path to a temporary file, returning a {@link File} instance with the path
|
||||
* contents.
|
||||
*/
|
||||
protected File downloadFile (String path)
|
||||
throws IOException
|
||||
protected File downloadFile (String path) throws IOException
|
||||
{
|
||||
File target = getLocalPath(path + "_new");
|
||||
|
||||
@@ -1643,26 +1632,7 @@ public class Application
|
||||
}
|
||||
|
||||
log.info("Attempting to refetch '" + path + "' from '" + targetURL + "'.");
|
||||
|
||||
// stream the URL into our temporary file
|
||||
URLConnection uconn = ConnectionUtil.open(proxy, targetURL, 0, 0);
|
||||
// we have to tell Java not to use caches here, otherwise it will cache any request for
|
||||
// same URL for the lifetime of this JVM (based on the URL string, not the URL object);
|
||||
// if the getdown.txt file, for example, changes in the meanwhile, we would never hear
|
||||
// about it; turning off caches is not a performance concern, because when Getdown asks
|
||||
// to download a file, it expects it to come over the wire, not from a cache
|
||||
uconn.setUseCaches(false);
|
||||
uconn.setRequestProperty("Accept-Encoding", "gzip");
|
||||
try (InputStream fin = uconn.getInputStream()) {
|
||||
String encoding = uconn.getContentEncoding();
|
||||
boolean gzip = "gzip".equalsIgnoreCase(encoding);
|
||||
try (InputStream fin2 = (gzip ? new GZIPInputStream(fin) : fin)) {
|
||||
try (FileOutputStream fout = new FileOutputStream(target)) {
|
||||
StreamUtil.copy(fin2, fout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conn.download(targetURL, target); // stream the URL into our temporary file
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +101,16 @@ public final class SysProps
|
||||
return Boolean.getBoolean("direct");
|
||||
}
|
||||
|
||||
/** If true, Getdown will always try to connect without proxy settings even it a proxy is set
|
||||
* in {@code proxy.txt}. If direct access is possible it will not clear {@code proxy.txt}, it
|
||||
* will preserve the settings. This is to support cases where a user uses a workstation in two
|
||||
* different networks, one with proxy the other one without. They should not be asked for
|
||||
* proxy settings again each time they switch back to the proxy network.
|
||||
* Usage: {@code -Dtry_no_proxy}. */
|
||||
public static boolean tryNoProxyFirst () {
|
||||
return Boolean.getBoolean("try_no_proxy");
|
||||
}
|
||||
|
||||
/** Specifies the connection timeout (in seconds) to use when downloading control files from
|
||||
* the server. This is chiefly useful when you are running in versionless mode and want Getdown
|
||||
* to more quickly timeout its startup update check if the server with which it is
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
//
|
||||
// Getdown - application installer, patcher and launcher
|
||||
// Copyright (C) 2004-2018 Getdown authors
|
||||
// https://github.com/threerings/getdown/blob/master/LICENSE
|
||||
|
||||
package com.threerings.getdown.net;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.threerings.getdown.data.SysProps;
|
||||
import com.threerings.getdown.util.Base64;
|
||||
import com.threerings.getdown.util.StreamUtil;
|
||||
|
||||
/**
|
||||
* Manages the process of making HTTP connections, using a proxy if necessary. Tracks and reports
|
||||
* when proxy credentials are rejected.
|
||||
*/
|
||||
public class Connector {
|
||||
|
||||
/** The default connector uses no proxy. */
|
||||
public static final Connector DEFAULT = new Connector(Proxy.NO_PROXY);
|
||||
|
||||
/** Tracks the state of a connector. If it fails for proxy-related reasons, it may transition
|
||||
* to a need_proxy or need_proxy_auth state. */
|
||||
public enum State { ACTIVE, NEED_PROXY, NEED_PROXY_AUTH }
|
||||
|
||||
/** The proxy used by this connector. */
|
||||
public final Proxy proxy;
|
||||
|
||||
/** The current state of this connector. */
|
||||
public State state = State.ACTIVE;
|
||||
|
||||
public Connector (Proxy proxy) {
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a connection to a URL, setting the authentication header if user info is present.
|
||||
* @param proxy the proxy via which to perform HTTP connections.
|
||||
* @param url the URL to which to open a connection.
|
||||
* @param connectTimeout if {@code > 0} then a timeout, in seconds, to use when opening the
|
||||
* connection. If {@code 0} is supplied, the connection timeout specified via system properties
|
||||
* will be used instead.
|
||||
* @param readTimeout if {@code > 0} then a timeout, in seconds, to use while reading data from
|
||||
* the connection. If {@code 0} is supplied, the read timeout specified via system properties
|
||||
* will be used instead.
|
||||
*/
|
||||
public URLConnection open (URL url, int connectTimeout, int readTimeout)
|
||||
throws IOException
|
||||
{
|
||||
URLConnection conn = url.openConnection(proxy);
|
||||
|
||||
// configure a connect timeout, if requested
|
||||
int ctimeout = connectTimeout > 0 ? connectTimeout : SysProps.connectTimeout();
|
||||
if (ctimeout > 0) {
|
||||
conn.setConnectTimeout(ctimeout * 1000);
|
||||
}
|
||||
|
||||
// configure a read timeout, if requested
|
||||
int rtimeout = readTimeout > 0 ? readTimeout : SysProps.readTimeout();
|
||||
if (rtimeout > 0) {
|
||||
conn.setReadTimeout(rtimeout * 1000);
|
||||
}
|
||||
|
||||
// If URL has a username:password@ before hostname, use HTTP basic auth
|
||||
String userInfo = url.getUserInfo();
|
||||
if (userInfo != null) {
|
||||
// Remove any percent-encoding in the username/password
|
||||
userInfo = URLDecoder.decode(userInfo, "UTF-8");
|
||||
// Now base64 encode the auth info and make it a single line
|
||||
String encoded = Base64.encodeToString(userInfo.getBytes(UTF_8), Base64.DEFAULT).
|
||||
replaceAll("\\n","").replaceAll("\\r", "");
|
||||
conn.setRequestProperty("Authorization", "Basic " + encoded);
|
||||
}
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a connection to a http or https URL, setting the authentication header if user info is
|
||||
* present. Throws a class cast exception if the connection returned is not the right type. See
|
||||
* {@link #open} for parameter documentation.
|
||||
*/
|
||||
public HttpURLConnection openHttp (URL url, int connectTimeout, int readTimeout)
|
||||
throws IOException
|
||||
{
|
||||
return (HttpURLConnection)open(url, connectTimeout, readTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads {@code url} into {@code target}.
|
||||
*/
|
||||
public void download (URL url, File target) throws IOException {
|
||||
URLConnection conn = open(url, 0, 0);
|
||||
// we have to tell Java not to use caches here, otherwise it will cache any request for
|
||||
// same URL for the lifetime of this JVM (based on the URL string, not the URL object);
|
||||
// if the getdown.txt file, for example, changes in the meanwhile, we would never hear
|
||||
// about it; turning off caches is not a performance concern, because when Getdown asks
|
||||
// to download a file, it expects it to come over the wire, not from a cache
|
||||
conn.setUseCaches(false);
|
||||
conn.setRequestProperty("Accept-Encoding", "gzip");
|
||||
checkConnectOK(conn, "Unable to download " + url);
|
||||
try (InputStream fin = conn.getInputStream()) {
|
||||
String encoding = conn.getContentEncoding();
|
||||
boolean gzip = "gzip".equalsIgnoreCase(encoding);
|
||||
try (InputStream fin2 = (gzip ? new GZIPInputStream(fin) : fin)) {
|
||||
try (FileOutputStream fout = new FileOutputStream(target)) {
|
||||
StreamUtil.copy(fin2, fout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the data at {@code url} into a string.
|
||||
*/
|
||||
public String fetch (URL url) throws IOException {
|
||||
URLConnection conn = open(url, 0, 0);
|
||||
checkConnectOK(conn, "Unable to fetch " + url);
|
||||
int size = conn.getContentLength();
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(size > 0 ? size : 1024);
|
||||
try (InputStream in = conn.getInputStream()) {
|
||||
StreamUtil.copy(in, out);
|
||||
}
|
||||
return out.toString(UTF_8.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that {@code conn} returned an {@code OK} response code iff it is an HTTP connection.
|
||||
* If the connection failed for proxy related reasons, this changes the state of this connector
|
||||
* to reflect the needed proxy information.
|
||||
*/
|
||||
public void checkConnectOK (URLConnection conn, String errpre) throws IOException
|
||||
{
|
||||
// if it's not an HTTP connection, there's nothing to check
|
||||
if (!(conn instanceof HttpURLConnection)) return;
|
||||
|
||||
int code = ((HttpURLConnection)conn).getResponseCode();
|
||||
switch (code) {
|
||||
case HttpURLConnection.HTTP_OK:
|
||||
return;
|
||||
case HttpURLConnection.HTTP_FORBIDDEN:
|
||||
case HttpURLConnection.HTTP_USE_PROXY:
|
||||
state = State.NEED_PROXY;
|
||||
break;
|
||||
case HttpURLConnection.HTTP_PROXY_AUTH:
|
||||
state = State.NEED_PROXY_AUTH;
|
||||
break;
|
||||
}
|
||||
throw new IOException(errpre + " [code=" + code + "]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds appropriate proxy args from this connector's configuration to the supplied list of
|
||||
* command line args that will be used to launch the app.
|
||||
*/
|
||||
public void addProxyArgs (List<String> args) {
|
||||
if (proxy.type() == Proxy.Type.HTTP && proxy.address() instanceof InetSocketAddress) {
|
||||
InetSocketAddress proxyAddr = (InetSocketAddress) proxy.address();
|
||||
String proxyHost = proxyAddr.getHostString();
|
||||
int proxyPort = proxyAddr.getPort();
|
||||
args.add("-Dhttp.proxyHost=" + proxyHost);
|
||||
args.add("-Dhttp.proxyPort=" + proxyPort);
|
||||
args.add("-Dhttps.proxyHost=" + proxyHost);
|
||||
args.add("-Dhttps.proxyPort=" + proxyPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,13 @@
|
||||
package com.threerings.getdown.net;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
@@ -26,8 +32,13 @@ import static com.threerings.getdown.Log.log;
|
||||
* implementors must take care to only execute thread-safe code or simply pass a message to the AWT
|
||||
* thread, for example.
|
||||
*/
|
||||
public abstract class Downloader
|
||||
public class Downloader
|
||||
{
|
||||
public Downloader (Connector conn)
|
||||
{
|
||||
_conn = conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the downloading process.
|
||||
* @param resources the resources to download.
|
||||
@@ -132,7 +143,22 @@ public abstract class Downloader
|
||||
/**
|
||||
* Performs the protocol-specific portion of checking download size.
|
||||
*/
|
||||
protected abstract long checkSize (Resource rsrc) throws IOException;
|
||||
protected long checkSize (Resource rsrc) throws IOException {
|
||||
URLConnection conn = _conn.open(rsrc.getRemote(), 0, 0);
|
||||
try {
|
||||
// if we're accessing our data via HTTP, we only need a HEAD request
|
||||
if (conn instanceof HttpURLConnection) {
|
||||
((HttpURLConnection)conn).setRequestMethod("HEAD");
|
||||
}
|
||||
// make sure we got a satisfactory response code
|
||||
_conn.checkConnectOK(conn, "Unable to check up-to-date for " + rsrc.getRemote());
|
||||
return conn.getContentLength();
|
||||
|
||||
} finally {
|
||||
// let it be known that we're done with this connection
|
||||
conn.getInputStream().close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Periodically called by the protocol-specific downloaders to update their progress. This
|
||||
@@ -203,13 +229,62 @@ public abstract class Downloader
|
||||
* protocol-specific code. This method should periodically check whether {@code _state} is set
|
||||
* to aborted and abort any in-progress download if so.
|
||||
*/
|
||||
protected abstract void download (Resource rsrc) throws IOException;
|
||||
protected void download (Resource rsrc) throws IOException {
|
||||
URLConnection conn = _conn.open(rsrc.getRemote(), 0, 0);
|
||||
// make sure we got a satisfactory response code
|
||||
_conn.checkConnectOK(conn, "Unable to download resource " + rsrc.getRemote());
|
||||
|
||||
// TODO: make FileChannel download impl (below) robust and allow apps to opt-into it via a
|
||||
// system property
|
||||
if (true) {
|
||||
// download the resource from the specified URL
|
||||
long actualSize = conn.getContentLength();
|
||||
log.info("Downloading resource", "url", rsrc.getRemote(), "size", actualSize);
|
||||
long currentSize = 0L;
|
||||
byte[] buffer = new byte[4*4096];
|
||||
try (InputStream in = conn.getInputStream();
|
||||
FileOutputStream out = new FileOutputStream(rsrc.getLocalNew())) {
|
||||
|
||||
// TODO: look to see if we have a download info file
|
||||
// containing info on potentially partially downloaded data;
|
||||
// if so, use a "Range: bytes=HAVE-" header.
|
||||
|
||||
// read in the file data
|
||||
int read;
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
// abort the download if the downloader is aborted
|
||||
if (_state == State.ABORTED) {
|
||||
break;
|
||||
}
|
||||
// write it out to our local copy
|
||||
out.write(buffer, 0, read);
|
||||
// note that we've downloaded some data
|
||||
currentSize += read;
|
||||
reportProgress(rsrc, currentSize, actualSize);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
log.info("Downloading resource", "url", rsrc.getRemote(), "size", "unknown");
|
||||
File localNew = rsrc.getLocalNew();
|
||||
try (ReadableByteChannel rbc = Channels.newChannel(conn.getInputStream());
|
||||
FileOutputStream fos = new FileOutputStream(localNew)) {
|
||||
// TODO: more work is needed here, transferFrom can fail to transfer the entire
|
||||
// file, in which case it's not clear what we're supposed to do.. call it again?
|
||||
// will it repeatedly fail?
|
||||
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||
reportProgress(rsrc, localNew.length(), localNew.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final Connector _conn;
|
||||
|
||||
/** The reported sizes of our resources. */
|
||||
protected Map<Resource, Long> _sizes = new HashMap<>();
|
||||
protected final Map<Resource, Long> _sizes = new HashMap<>();
|
||||
|
||||
/** The bytes downloaded for each resource. */
|
||||
protected Map<Resource, Long> _downloaded = new HashMap<>();
|
||||
protected final Map<Resource, Long> _downloaded = new HashMap<>();
|
||||
|
||||
/** The time at which the file transfer began. */
|
||||
protected long _start;
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
//
|
||||
// Getdown - application installer, patcher and launcher
|
||||
// Copyright (C) 2004-2018 Getdown authors
|
||||
// https://github.com/threerings/getdown/blob/master/LICENSE
|
||||
|
||||
package com.threerings.getdown.net;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
|
||||
import com.threerings.getdown.data.Resource;
|
||||
import com.threerings.getdown.util.ConnectionUtil;
|
||||
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
/**
|
||||
* Implements downloading files over HTTP
|
||||
*/
|
||||
public class HTTPDownloader extends Downloader
|
||||
{
|
||||
public HTTPDownloader (Proxy proxy)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
@Override protected long checkSize (Resource rsrc) throws IOException
|
||||
{
|
||||
URLConnection conn = ConnectionUtil.open(_proxy, rsrc.getRemote(), 0, 0);
|
||||
try {
|
||||
// if we're accessing our data via HTTP, we only need a HEAD request
|
||||
if (conn instanceof HttpURLConnection) {
|
||||
HttpURLConnection hcon = (HttpURLConnection)conn;
|
||||
hcon.setRequestMethod("HEAD");
|
||||
hcon.connect();
|
||||
// make sure we got a satisfactory response code
|
||||
if (hcon.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
throw new IOException("Unable to check up-to-date for " +
|
||||
rsrc.getRemote() + ": " + hcon.getResponseCode());
|
||||
}
|
||||
}
|
||||
return conn.getContentLength();
|
||||
|
||||
} finally {
|
||||
// let it be known that we're done with this connection
|
||||
conn.getInputStream().close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected void download (Resource rsrc) throws IOException
|
||||
{
|
||||
// TODO: make FileChannel download impl (below) robust and allow apps to opt-into it via a
|
||||
// system property
|
||||
if (true) {
|
||||
// download the resource from the specified URL
|
||||
URLConnection conn = ConnectionUtil.open(_proxy, rsrc.getRemote(), 0, 0);
|
||||
conn.connect();
|
||||
|
||||
// make sure we got a satisfactory response code
|
||||
if (conn instanceof HttpURLConnection) {
|
||||
HttpURLConnection hcon = (HttpURLConnection)conn;
|
||||
if (hcon.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
throw new IOException("Unable to download resource " + rsrc.getRemote() + ": " +
|
||||
hcon.getResponseCode());
|
||||
}
|
||||
}
|
||||
long actualSize = conn.getContentLength();
|
||||
log.info("Downloading resource", "url", rsrc.getRemote(), "size", actualSize);
|
||||
long currentSize = 0L;
|
||||
byte[] buffer = new byte[4*4096];
|
||||
try (InputStream in = conn.getInputStream();
|
||||
FileOutputStream out = new FileOutputStream(rsrc.getLocalNew())) {
|
||||
|
||||
// TODO: look to see if we have a download info file
|
||||
// containing info on potentially partially downloaded data;
|
||||
// if so, use a "Range: bytes=HAVE-" header.
|
||||
|
||||
// read in the file data
|
||||
int read;
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
// abort the download if the downloader is aborted
|
||||
if (_state == State.ABORTED) {
|
||||
break;
|
||||
}
|
||||
// write it out to our local copy
|
||||
out.write(buffer, 0, read);
|
||||
// note that we've downloaded some data
|
||||
currentSize += read;
|
||||
reportProgress(rsrc, currentSize, actualSize);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
log.info("Downloading resource", "url", rsrc.getRemote(), "size", "unknown");
|
||||
File localNew = rsrc.getLocalNew();
|
||||
try (ReadableByteChannel rbc = Channels.newChannel(rsrc.getRemote().openStream());
|
||||
FileOutputStream fos = new FileOutputStream(localNew)) {
|
||||
// TODO: more work is needed here, transferFrom can fail to transfer the entire
|
||||
// file, in which case it's not clear what we're supposed to do.. call it again?
|
||||
// will it repeatedly fail?
|
||||
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||
reportProgress(rsrc, localNew.length(), localNew.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final Proxy _proxy;
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
//
|
||||
// Getdown - application installer, patcher and launcher
|
||||
// Copyright (C) 2004-2018 Getdown authors
|
||||
// https://github.com/threerings/getdown/blob/master/LICENSE
|
||||
|
||||
package com.threerings.getdown.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLDecoder;
|
||||
|
||||
import com.threerings.getdown.data.SysProps;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
public final class ConnectionUtil
|
||||
{
|
||||
/**
|
||||
* Opens a connection to a URL, setting the authentication header if user info is present.
|
||||
* @param proxy the proxy via which to perform HTTP connections.
|
||||
* @param url the URL to which to open a connection.
|
||||
* @param connectTimeout if {@code > 0} then a timeout, in seconds, to use when opening the
|
||||
* connection. If {@code 0} is supplied, the connection timeout specified via system properties
|
||||
* will be used instead.
|
||||
* @param readTimeout if {@code > 0} then a timeout, in seconds, to use while reading data from
|
||||
* the connection. If {@code 0} is supplied, the read timeout specified via system properties
|
||||
* will be used instead.
|
||||
*/
|
||||
public static URLConnection open (Proxy proxy, URL url, int connectTimeout, int readTimeout)
|
||||
throws IOException
|
||||
{
|
||||
URLConnection conn = url.openConnection(proxy);
|
||||
|
||||
// configure a connect timeout, if requested
|
||||
int ctimeout = connectTimeout > 0 ? connectTimeout : SysProps.connectTimeout();
|
||||
if (ctimeout > 0) {
|
||||
conn.setConnectTimeout(ctimeout * 1000);
|
||||
}
|
||||
|
||||
// configure a read timeout, if requested
|
||||
int rtimeout = readTimeout > 0 ? readTimeout : SysProps.readTimeout();
|
||||
if (rtimeout > 0) {
|
||||
conn.setReadTimeout(rtimeout * 1000);
|
||||
}
|
||||
|
||||
// If URL has a username:password@ before hostname, use HTTP basic auth
|
||||
String userInfo = url.getUserInfo();
|
||||
if (userInfo != null) {
|
||||
// Remove any percent-encoding in the username/password
|
||||
userInfo = URLDecoder.decode(userInfo, "UTF-8");
|
||||
// Now base64 encode the auth info and make it a single line
|
||||
String encoded = Base64.encodeToString(userInfo.getBytes(UTF_8), Base64.DEFAULT).
|
||||
replaceAll("\\n","").replaceAll("\\r", "");
|
||||
conn.setRequestProperty("Authorization", "Basic " + encoded);
|
||||
}
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a connection to a http or https URL, setting the authentication header if user info is
|
||||
* present. Throws a class cast exception if the connection returned is not the right type. See
|
||||
* {@link #open} for parameter documentation.
|
||||
*/
|
||||
public static HttpURLConnection openHttp (
|
||||
Proxy proxy, URL url, int connectTimeout, int readTimeout) throws IOException
|
||||
{
|
||||
return (HttpURLConnection)open(proxy, url, connectTimeout, readTimeout);
|
||||
}
|
||||
}
|
||||
@@ -39,17 +39,16 @@ import javax.swing.JFrame;
|
||||
import javax.swing.JLayeredPane;
|
||||
|
||||
import com.samskivert.swing.util.SwingUtil;
|
||||
import com.threerings.getdown.data.Application;
|
||||
import com.threerings.getdown.data.Application.UpdateInterface.Step;
|
||||
import com.threerings.getdown.data.Application;
|
||||
import com.threerings.getdown.data.Build;
|
||||
import com.threerings.getdown.data.EnvConfig;
|
||||
import com.threerings.getdown.data.Resource;
|
||||
import com.threerings.getdown.data.SysProps;
|
||||
import com.threerings.getdown.net.Connector;
|
||||
import com.threerings.getdown.net.Downloader;
|
||||
import com.threerings.getdown.net.HTTPDownloader;
|
||||
import com.threerings.getdown.tools.Patcher;
|
||||
import com.threerings.getdown.util.Config;
|
||||
import com.threerings.getdown.util.ConnectionUtil;
|
||||
import com.threerings.getdown.util.FileUtil;
|
||||
import com.threerings.getdown.util.LaunchUtil;
|
||||
import com.threerings.getdown.util.MessageUtil;
|
||||
@@ -62,12 +61,22 @@ import static com.threerings.getdown.Log.log;
|
||||
/**
|
||||
* Manages the main control for the Getdown application updater and deployment system.
|
||||
*/
|
||||
public abstract class Getdown extends Thread
|
||||
public abstract class Getdown
|
||||
implements Application.StatusDisplay, RotatingBackgrounds.ImageLoader
|
||||
{
|
||||
/**
|
||||
* Starts a thread to run Getdown and ultimately (hopefully) launch the target app.
|
||||
*/
|
||||
public static void run (final Getdown getdown) {
|
||||
new Thread("Getdown") {
|
||||
@Override public void run () {
|
||||
getdown.run();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
public Getdown (EnvConfig envc)
|
||||
{
|
||||
super("Getdown");
|
||||
try {
|
||||
// If the silent property exists, install without bringing up any gui. If it equals
|
||||
// launch, start the application after installing. Otherwise, just install and exit.
|
||||
@@ -134,8 +143,28 @@ public abstract class Getdown extends Thread
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run ()
|
||||
/**
|
||||
* Configures our proxy settings (called by {@link ProxyPanel}) and fires up the launcher.
|
||||
*/
|
||||
public void configProxy (String host, String port, String username, String password)
|
||||
{
|
||||
log.info("User configured proxy", "host", host, "port", port);
|
||||
ProxyUtil.configProxy(_app, host, port, username, password);
|
||||
|
||||
// clear out our UI
|
||||
disposeContainer();
|
||||
_container = null;
|
||||
|
||||
// fire up a new thread
|
||||
run(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* The main entry point of Getdown: does some sanity checks and preparation, then delegates the
|
||||
* actual getting down to {@link #getdown}. This is not called directly, but rather via the
|
||||
* static {@code run} method as Getdown does its main work on a separate thread.
|
||||
*/
|
||||
protected void run ()
|
||||
{
|
||||
// if we have no messages, just bail because we're hosed; the error message will be
|
||||
// displayed to the user already
|
||||
@@ -156,59 +185,20 @@ public abstract class Getdown extends Thread
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
_dead = false;
|
||||
// if we fail to detect a proxy, but we're allowed to run offline, then go ahead and
|
||||
// run the app anyway because we're prepared to cope with not being able to update
|
||||
if (detectProxy() || _app.allowOffline()) {
|
||||
getdown();
|
||||
} else if (_silent) {
|
||||
log.warning("Need a proxy, but we don't want to bother anyone. Exiting.");
|
||||
} else {
|
||||
// create a panel they can use to configure the proxy settings
|
||||
_container = createContainer();
|
||||
// allow them to close the window to abort the proxy configuration
|
||||
_dead = true;
|
||||
configureContainer();
|
||||
ProxyPanel panel = new ProxyPanel(this, _msgs);
|
||||
// set up any existing configured proxy
|
||||
String[] hostPort = ProxyUtil.loadProxy(_app);
|
||||
panel.setProxy(hostPort[0], hostPort[1]);
|
||||
_container.add(panel, BorderLayout.CENTER);
|
||||
showContainer();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warning("run() failed.", e);
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures our proxy settings (called by {@link ProxyPanel}) and fires up the launcher.
|
||||
*/
|
||||
public void configProxy (String host, String port, String username, String password)
|
||||
{
|
||||
log.info("User configured proxy", "host", host, "port", port);
|
||||
|
||||
if (!StringUtil.isBlank(host)) {
|
||||
ProxyUtil.configProxy(_app, host, port, username, password);
|
||||
}
|
||||
|
||||
// clear out our UI
|
||||
disposeContainer();
|
||||
_container = null;
|
||||
|
||||
// fire up a new thread
|
||||
new Thread(this).start();
|
||||
_dead = false;
|
||||
// if we fail to detect a proxy, but we're allowed to run offline, then go ahead and
|
||||
// run the app anyway because we're prepared to cope with not being able to update
|
||||
if (detectProxy() || _app.allowOffline()) getdown();
|
||||
else requestProxyInfo(false);
|
||||
}
|
||||
|
||||
protected boolean detectProxy () {
|
||||
if (ProxyUtil.autoDetectProxy(_app)) {
|
||||
boolean tryNoProxy = SysProps.tryNoProxyFirst();
|
||||
if (!tryNoProxy && ProxyUtil.autoDetectProxy(_app)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// otherwise see if we actually need a proxy; first we have to initialize our application
|
||||
// see if we actually need a proxy; first we have to initialize our application
|
||||
// to get some sort of interface configuration and the appbase URL
|
||||
log.info("Checking whether we need to use a proxy...");
|
||||
try {
|
||||
@@ -217,14 +207,18 @@ public abstract class Getdown extends Thread
|
||||
// no worries
|
||||
}
|
||||
updateStatus("m.detecting_proxy");
|
||||
if (!ProxyUtil.canLoadWithoutProxy(_app.getConfigResource().getRemote())) {
|
||||
return false;
|
||||
URL configURL = _app.getConfigResource().getRemote();
|
||||
if (!ProxyUtil.canLoadWithoutProxy(configURL, tryNoProxy ? 2 : 5)) {
|
||||
// if we didn't auto-detect proxy first thing, do auto-detect now
|
||||
return tryNoProxy ? ProxyUtil.autoDetectProxy(_app) : false;
|
||||
}
|
||||
|
||||
// we got through, so we appear not to require a proxy; make a blank proxy config so that
|
||||
// we don't go through this whole detection process again next time
|
||||
log.info("No proxy appears to be needed.");
|
||||
ProxyUtil.saveProxy(_app, null, null);
|
||||
if (!tryNoProxy) {
|
||||
// we got through, so we appear not to require a proxy; make a blank proxy config so
|
||||
// that we don't go through this whole detection process again next time
|
||||
ProxyUtil.saveProxy(_app, null, null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -234,6 +228,25 @@ public abstract class Getdown extends Thread
|
||||
_ifc = new Application.UpdateInterface(config);
|
||||
}
|
||||
|
||||
protected void requestProxyInfo (boolean reinitAuth) {
|
||||
if (_silent) {
|
||||
log.warning("Need a proxy, but we don't want to bother anyone. Exiting.");
|
||||
return;
|
||||
}
|
||||
|
||||
// create a panel they can use to configure the proxy settings
|
||||
_container = createContainer();
|
||||
// allow them to close the window to abort the proxy configuration
|
||||
_dead = true;
|
||||
configureContainer();
|
||||
ProxyPanel panel = new ProxyPanel(this, _msgs, reinitAuth);
|
||||
// set up any existing configured proxy
|
||||
String[] hostPort = ProxyUtil.loadProxy(_app);
|
||||
panel.setProxy(hostPort[0], hostPort[1]);
|
||||
_container.add(panel, BorderLayout.CENTER);
|
||||
showContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads and installs (without verifying) any resources that are marked with a
|
||||
* {@code PRELOAD} attribute.
|
||||
@@ -264,7 +277,7 @@ public abstract class Getdown extends Thread
|
||||
protected void getdown ()
|
||||
{
|
||||
try {
|
||||
// first parses our application deployment file
|
||||
// first parse our application deployment file
|
||||
try {
|
||||
readConfig(true);
|
||||
} catch (IOException ioe) {
|
||||
@@ -279,7 +292,7 @@ public abstract class Getdown extends Thread
|
||||
throw new MultipleGetdownRunning();
|
||||
}
|
||||
|
||||
// Update the config modtime so a sleeping getdown will notice the change.
|
||||
// update the config modtime so a sleeping getdown will notice the change
|
||||
File config = _app.getLocalPath(Application.CONFIG_FILE);
|
||||
if (!config.setLastModified(System.currentTimeMillis())) {
|
||||
log.warning("Unable to set modtime on config file, will be unable to check for " +
|
||||
@@ -424,9 +437,20 @@ public abstract class Getdown extends Thread
|
||||
throw new IOException("m.unable_to_repair");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warning("getdown() failed.", e);
|
||||
fail(e);
|
||||
_app.releaseLock();
|
||||
// if we failed due to proxy errors, ask for proxy info
|
||||
switch (_app.conn.state) {
|
||||
case NEED_PROXY:
|
||||
requestProxyInfo(false);
|
||||
break;
|
||||
case NEED_PROXY_AUTH:
|
||||
requestProxyInfo(true);
|
||||
break;
|
||||
default:
|
||||
log.warning("getdown() failed.", e);
|
||||
fail(e);
|
||||
_app.releaseLock();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -607,7 +631,7 @@ public abstract class Getdown extends Thread
|
||||
// create our user interface
|
||||
createInterfaceAsync(false);
|
||||
|
||||
Downloader dl = new HTTPDownloader(_app.proxy) {
|
||||
Downloader dl = new Downloader(_app.conn) {
|
||||
@Override protected void resolvingDownloads () {
|
||||
updateStatus("m.resolving");
|
||||
}
|
||||
@@ -841,11 +865,12 @@ public abstract class Getdown extends Thread
|
||||
msg = MessageUtil.compose("m.unknown_error", _ifc.installError);
|
||||
} else if (!msg.startsWith("m.")) {
|
||||
// try to do something sensible based on the type of error
|
||||
msg = e instanceof FileNotFoundException
|
||||
? MessageUtil.compose("m.missing_resource", MessageUtil.taint(msg), _ifc.installError)
|
||||
: MessageUtil.compose("m.init_error", MessageUtil.taint(msg), _ifc.installError);
|
||||
msg = MessageUtil.taint(msg);
|
||||
msg = e instanceof FileNotFoundException ?
|
||||
MessageUtil.compose("m.missing_resource", msg, _ifc.installError) :
|
||||
MessageUtil.compose("m.init_error", msg, _ifc.installError);
|
||||
}
|
||||
// Since we're dead, clear off the 'time remaining' label along with displaying the error message
|
||||
// since we're dead, clear off the 'time remaining' label along with displaying the error
|
||||
fail(msg);
|
||||
}
|
||||
|
||||
@@ -931,14 +956,14 @@ public abstract class Getdown extends Thread
|
||||
do {
|
||||
URL url = _app.getTrackingProgressURL(++_reportedProgress);
|
||||
if (url != null) {
|
||||
new ProgressReporter(url).start();
|
||||
reportProgress(url);
|
||||
}
|
||||
} while (_reportedProgress <= progress);
|
||||
|
||||
} else {
|
||||
URL url = _app.getTrackingURL(event);
|
||||
if (url != null) {
|
||||
new ProgressReporter(url).start();
|
||||
reportProgress(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1001,44 +1026,40 @@ public abstract class Getdown extends Thread
|
||||
}
|
||||
|
||||
/** Used to fetch a progress report URL. */
|
||||
protected class ProgressReporter extends Thread
|
||||
{
|
||||
public ProgressReporter (URL url) {
|
||||
setDaemon(true);
|
||||
_url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run () {
|
||||
try {
|
||||
HttpURLConnection ucon = ConnectionUtil.openHttp(_app.proxy, _url, 0, 0);
|
||||
|
||||
// if we have a tracking cookie configured, configure the request with it
|
||||
if (_app.getTrackingCookieName() != null &&
|
||||
_app.getTrackingCookieProperty() != null) {
|
||||
String val = System.getProperty(_app.getTrackingCookieProperty());
|
||||
if (val != null) {
|
||||
ucon.setRequestProperty("Cookie", _app.getTrackingCookieName() + "=" + val);
|
||||
}
|
||||
}
|
||||
|
||||
// now request our tracking URL and ensure that we get a non-error response
|
||||
ucon.connect();
|
||||
protected void reportProgress (final URL url) {
|
||||
Thread reporter = new Thread("Progress reporter") {
|
||||
public void run () {
|
||||
try {
|
||||
if (ucon.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
log.warning("Failed to report tracking event",
|
||||
"url", _url, "rcode", ucon.getResponseCode());
|
||||
HttpURLConnection ucon = _app.conn.openHttp(url, 0, 0);
|
||||
|
||||
// if we have a tracking cookie configured, configure the request with it
|
||||
if (_app.getTrackingCookieName() != null &&
|
||||
_app.getTrackingCookieProperty() != null) {
|
||||
String val = System.getProperty(_app.getTrackingCookieProperty());
|
||||
if (val != null) {
|
||||
ucon.setRequestProperty(
|
||||
"Cookie", _app.getTrackingCookieName() + "=" + val);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
ucon.disconnect();
|
||||
|
||||
// now request our tracking URL and ensure that we get a non-error response
|
||||
ucon.connect();
|
||||
try {
|
||||
if (ucon.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
log.warning("Failed to report tracking event",
|
||||
"url", url, "rcode", ucon.getResponseCode());
|
||||
}
|
||||
} finally {
|
||||
ucon.disconnect();
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failed to report tracking event", "url", url, "error", ioe);
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
log.warning("Failed to report tracking event", "url", _url, "error", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
protected URL _url;
|
||||
};
|
||||
reporter.setDaemon(true);
|
||||
reporter.start();
|
||||
}
|
||||
|
||||
/** Used to pass progress on to our user interface. */
|
||||
|
||||
@@ -101,7 +101,7 @@ public class GetdownApp
|
||||
log.info("-- Cur dir: " + System.getProperty("user.dir"));
|
||||
log.info("---------------------------------------------");
|
||||
|
||||
Getdown app = new Getdown(envc) {
|
||||
Getdown getdown = new Getdown(envc) {
|
||||
@Override
|
||||
protected Container createContainer () {
|
||||
// create our user interface, and display it
|
||||
@@ -226,8 +226,7 @@ public class GetdownApp
|
||||
// super.fail causes the UI to be created (if needed) on the next UI tick, so we
|
||||
// want to wait until that happens before we attempt to redecorate the window
|
||||
EventQueue.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@Override public void run () {
|
||||
// if the frame was set to be undecorated, make window decoration available
|
||||
// to allow the user to close the window
|
||||
if (_frame != null && _frame.isUndecorated()) {
|
||||
@@ -247,7 +246,7 @@ public class GetdownApp
|
||||
|
||||
protected JFrame _frame;
|
||||
};
|
||||
app.start();
|
||||
return app;
|
||||
Getdown.run(getdown);
|
||||
return getdown;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,14 +35,16 @@ import static com.threerings.getdown.Log.log;
|
||||
*/
|
||||
public final class ProxyPanel extends JPanel implements ActionListener
|
||||
{
|
||||
public ProxyPanel (Getdown getdown, ResourceBundle msgs)
|
||||
public ProxyPanel (Getdown getdown, ResourceBundle msgs, boolean updateAuth)
|
||||
{
|
||||
_getdown = getdown;
|
||||
_msgs = msgs;
|
||||
_updateAuth = updateAuth;
|
||||
|
||||
setLayout(new VGroupLayout());
|
||||
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
add(new SaneLabelField(get("m.configure_proxy")));
|
||||
String title = get(updateAuth ? "m.update_proxy_auth" : "m.configure_proxy");
|
||||
add(new SaneLabelField(title));
|
||||
add(new Spacer(5, 5));
|
||||
|
||||
JPanel row = new JPanel(new GridLayout());
|
||||
@@ -61,19 +63,20 @@ public final class ProxyPanel extends JPanel implements ActionListener
|
||||
row.add(new SaneLabelField(get("m.proxy_auth_required")), BorderLayout.WEST);
|
||||
_useAuth = new JCheckBox();
|
||||
row.add(_useAuth);
|
||||
_useAuth.setSelected(updateAuth);
|
||||
add(row);
|
||||
|
||||
row = new JPanel(new GridLayout());
|
||||
row.add(new SaneLabelField(get("m.proxy_username")), BorderLayout.WEST);
|
||||
_username = new SaneTextField();
|
||||
_username.setEnabled(false);
|
||||
_username.setEnabled(updateAuth);
|
||||
row.add(_username);
|
||||
add(row);
|
||||
|
||||
row = new JPanel(new GridLayout());
|
||||
row.add(new SaneLabelField(get("m.proxy_password")), BorderLayout.WEST);
|
||||
_password = new SanePasswordField();
|
||||
_password.setEnabled(false);
|
||||
_password.setEnabled(updateAuth);
|
||||
row.add(_password);
|
||||
add(row);
|
||||
|
||||
@@ -112,7 +115,13 @@ public final class ProxyPanel extends JPanel implements ActionListener
|
||||
public void addNotify ()
|
||||
{
|
||||
super.addNotify();
|
||||
_host.requestFocusInWindow();
|
||||
if (_updateAuth) {
|
||||
// we are asking the user to update the credentials for an existing proxy
|
||||
// configuration, so focus that instead of the proxy host config
|
||||
_username.requestFocusInWindow();
|
||||
} else {
|
||||
_host.requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
@@ -184,8 +193,9 @@ public final class ProxyPanel extends JPanel implements ActionListener
|
||||
return dim;
|
||||
}
|
||||
|
||||
protected Getdown _getdown;
|
||||
protected ResourceBundle _msgs;
|
||||
protected final Getdown _getdown;
|
||||
protected final ResourceBundle _msgs;
|
||||
protected final boolean _updateAuth;
|
||||
|
||||
protected JTextField _host;
|
||||
protected JTextField _port;
|
||||
|
||||
@@ -34,9 +34,9 @@ import ca.beq.util.win32.registry.RegistryValue;
|
||||
import ca.beq.util.win32.registry.RootKey;
|
||||
|
||||
import com.threerings.getdown.data.Application;
|
||||
import com.threerings.getdown.net.Connector;
|
||||
import com.threerings.getdown.spi.ProxyAuth;
|
||||
import com.threerings.getdown.util.Config;
|
||||
import com.threerings.getdown.util.ConnectionUtil;
|
||||
import com.threerings.getdown.util.LaunchUtil;
|
||||
import com.threerings.getdown.util.StringUtil;
|
||||
|
||||
@@ -121,32 +121,31 @@ public final class ProxyUtil {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean canLoadWithoutProxy (URL rurl)
|
||||
public static boolean canLoadWithoutProxy (URL rurl, int timeoutSeconds)
|
||||
{
|
||||
log.info("Testing whether proxy is needed, via: " + rurl);
|
||||
log.info("Attempting to fetch without proxy: " + rurl);
|
||||
try {
|
||||
// try to make a HEAD request for this URL (use short connect and read timeouts)
|
||||
URLConnection conn = ConnectionUtil.open(Proxy.NO_PROXY, rurl, 5, 5);
|
||||
if (conn instanceof HttpURLConnection) {
|
||||
HttpURLConnection hcon = (HttpURLConnection)conn;
|
||||
try {
|
||||
hcon.setRequestMethod("HEAD");
|
||||
hcon.connect();
|
||||
// make sure we got a satisfactory response code
|
||||
int rcode = hcon.getResponseCode();
|
||||
if (rcode == HttpURLConnection.HTTP_PROXY_AUTH ||
|
||||
rcode == HttpURLConnection.HTTP_FORBIDDEN) {
|
||||
log.warning("Got an 'HTTP credentials needed' response", "code", rcode);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} finally {
|
||||
hcon.disconnect();
|
||||
}
|
||||
} else {
|
||||
// if the appbase is not an HTTP/S URL (like file:), then we don't need a proxy
|
||||
URLConnection conn = Connector.DEFAULT.open(rurl, timeoutSeconds, timeoutSeconds);
|
||||
// if the appbase is not an HTTP/S URL (like file:), then we don't need a proxy
|
||||
if (!(conn instanceof HttpURLConnection)) {
|
||||
return true;
|
||||
}
|
||||
// otherwise, try to make a HEAD request for this URL
|
||||
HttpURLConnection hcon = (HttpURLConnection)conn;
|
||||
try {
|
||||
hcon.setRequestMethod("HEAD");
|
||||
hcon.connect();
|
||||
// make sure we got a satisfactory response code
|
||||
int rcode = hcon.getResponseCode();
|
||||
if (rcode == HttpURLConnection.HTTP_PROXY_AUTH ||
|
||||
rcode == HttpURLConnection.HTTP_FORBIDDEN) {
|
||||
log.warning("Got an 'HTTP credentials needed' response", "code", rcode);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} finally {
|
||||
hcon.disconnect();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
log.info("Failed to HEAD " + rurl + ": " + ioe);
|
||||
log.info("We probably need a proxy, but auto-detection failed.");
|
||||
@@ -214,9 +213,14 @@ public final class ProxyUtil {
|
||||
}
|
||||
boolean haveCreds = !StringUtil.isBlank(username) && !StringUtil.isBlank(password);
|
||||
|
||||
int pport = StringUtil.isBlank(port) ? 80 : Integer.valueOf(port);
|
||||
log.info("Using proxy", "host", host, "port", pport, "haveCreds", haveCreds);
|
||||
app.proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, pport));
|
||||
if (StringUtil.isBlank(host)) {
|
||||
log.info("Using no proxy");
|
||||
app.conn = new Connector(Proxy.NO_PROXY);
|
||||
} else {
|
||||
int pp = StringUtil.isBlank(port) ? 80 : Integer.valueOf(port);
|
||||
log.info("Using proxy", "host", host, "port", pp, "haveCreds", haveCreds);
|
||||
app.conn = new Connector(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, pp)));
|
||||
}
|
||||
|
||||
if (haveCreds) {
|
||||
final String fuser = username;
|
||||
|
||||
@@ -17,6 +17,9 @@ m.configure_proxy = <html>We were unable to connect to the application server to
|
||||
<p> Your computer may access the Internet through a proxy and we were unable to automatically \
|
||||
detect your proxy settings. If you know your proxy settings, you can enter them below.</html>
|
||||
|
||||
m.update_proxy_auth = <html>The stored proxy user/password is wrong or obsolete. \
|
||||
<p>Please provide an updated user/password combination.</html>
|
||||
|
||||
m.proxy_extra = <html>If you are sure that you don't use a proxy then \
|
||||
perhaps there is a temporary Internet outage that is preventing us from \
|
||||
communicating with the servers. In this case, you can cancel and try \
|
||||
|
||||
@@ -16,6 +16,9 @@ m.configure_proxy = <html>Es konnte keine Verbindung zum Applikations-Server auf
|
||||
Wenn kein Proxy verwendet werden soll, löschen Sie bitte alle Einträge in den unten \
|
||||
stehenden Feldern und klicken sie auf OK.</html>
|
||||
|
||||
m.update_proxy_auth = <html>Gespeicherte Proxy User/Passwort Kombination ungültig oder obsolet. \
|
||||
<p>Bitte geben Sie die korrekte User/Passwort Kombination ein.</html>
|
||||
|
||||
m.proxy_extra = <html>Sollten Sie keine Proxyeinstellungen gesetzt haben wenden Sie sich bitte \
|
||||
an Ihren Administrator.</html>
|
||||
|
||||
|
||||
@@ -20,7 +20,11 @@ m.configure_proxy = <html>Impossibile collegarsi al server per \
|
||||
questo potrebbe non essere stato riconosciuto automaticamente. Se conosci le \
|
||||
tue impostazioni del proxy, puoi inserirle di seguito.</html>
|
||||
|
||||
m.proxy_extra = <html>Se sei sicuro di non usare proxy \
|
||||
m.update_proxy_auth = <html>Combinazione User/Password salvata per il proxy non è valida \
|
||||
oppure obsoleta. \
|
||||
<p>Perfavore inserire una combinazione User/Password valida.</html>
|
||||
|
||||
m.proxy_extra = <html>Se sei sicuro di non usare proxy \
|
||||
potrebbe essere un problema di internet o di collegamento con il server. \
|
||||
In questo caso puoi annullare e ripetere l'installazione più tardi.</html>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user