Added support for proxy auth.

This also makes the use of a Proxy instance explicit in the Getdown code,
rather than trying to thread things through the proxy-related system
properties.

It introduces a ProxyAuth SPI which allows an app installation to save proxy
credentials in whatever way it deems fit. Otherwise the credentials are unsaved
and the user effectively has to enter them every time Getdown attempts to
access the internet.

This is obviously a terrible design, but there's no good (cross-platform) way
to securely store a username and password. So anyone using this feature is
likely going to need to provide an implementation of ProxyAuth that does
whatever auth storage they deem appropriate.

Adapted from PR from @ThomasG-AI, thanks!
This commit is contained in:
Michael Bayne
2019-01-15 12:02:10 -08:00
parent dc768515d5
commit ae129fad6f
16 changed files with 409 additions and 214 deletions
@@ -8,6 +8,7 @@ package com.threerings.getdown.data;
import java.io.*; import java.io.*;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.net.URLConnection; import java.net.URLConnection;
@@ -231,6 +232,11 @@ public class Application
} }
} }
/** 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
* getter and setter just to pretend like that's not the case. */
public Proxy proxy = Proxy.NO_PROXY;
/** /**
* Creates an application instance which records the location of the <code>getdown.txt</code> * Creates an application instance which records the location of the <code>getdown.txt</code>
* configuration file from the supplied application directory. * configuration file from the supplied application directory.
@@ -1208,7 +1214,7 @@ public class Application
} }
if (_latest != null) { if (_latest != null) {
try (InputStream in = ConnectionUtil.open(_latest, 0, 0).getInputStream(); try (InputStream in = ConnectionUtil.open(proxy, _latest, 0, 0).getInputStream();
InputStreamReader reader = new InputStreamReader(in, UTF_8); InputStreamReader reader = new InputStreamReader(in, UTF_8);
BufferedReader bin = new BufferedReader(reader)) { BufferedReader bin = new BufferedReader(reader)) {
for (String[] pair : Config.parsePairs(bin, Config.createOpts(false))) { for (String[] pair : Config.parsePairs(bin, Config.createOpts(false))) {
@@ -1592,7 +1598,7 @@ public class Application
log.info("Attempting to refetch '" + path + "' from '" + targetURL + "'."); log.info("Attempting to refetch '" + path + "' from '" + targetURL + "'.");
// stream the URL into our temporary file // stream the URL into our temporary file
URLConnection uconn = ConnectionUtil.open(targetURL, 0, 0); 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 // 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); // 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 // if the getdown.txt file, for example, changes in the meanwhile, we would never hear
@@ -10,6 +10,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.nio.channels.Channels; import java.nio.channels.Channels;
@@ -25,9 +26,14 @@ import static com.threerings.getdown.Log.log;
*/ */
public class HTTPDownloader extends Downloader public class HTTPDownloader extends Downloader
{ {
public HTTPDownloader (Proxy proxy)
{
_proxy = proxy;
}
@Override protected long checkSize (Resource rsrc) throws IOException @Override protected long checkSize (Resource rsrc) throws IOException
{ {
URLConnection conn = ConnectionUtil.open(rsrc.getRemote(), 0, 0); URLConnection conn = ConnectionUtil.open(_proxy, rsrc.getRemote(), 0, 0);
try { try {
// if we're accessing our data via HTTP, we only need a HEAD request // if we're accessing our data via HTTP, we only need a HEAD request
if (conn instanceof HttpURLConnection) { if (conn instanceof HttpURLConnection) {
@@ -54,7 +60,7 @@ public class HTTPDownloader extends Downloader
// system property // system property
if (true) { if (true) {
// download the resource from the specified URL // download the resource from the specified URL
URLConnection conn = ConnectionUtil.open(rsrc.getRemote(), 0, 0); URLConnection conn = ConnectionUtil.open(_proxy, rsrc.getRemote(), 0, 0);
conn.connect(); conn.connect();
// make sure we got a satisfactory response code // make sure we got a satisfactory response code
@@ -65,7 +71,6 @@ public class HTTPDownloader extends Downloader
hcon.getResponseCode()); hcon.getResponseCode());
} }
} }
long actualSize = conn.getContentLength(); long actualSize = conn.getContentLength();
log.info("Downloading resource", "url", rsrc.getRemote(), "size", actualSize); log.info("Downloading resource", "url", rsrc.getRemote(), "size", actualSize);
long currentSize = 0L; long currentSize = 0L;
@@ -105,4 +110,6 @@ public class HTTPDownloader extends Downloader
} }
} }
} }
protected final Proxy _proxy;
} }
@@ -0,0 +1,32 @@
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2018 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.spi;
/**
* A service provider interface that handles the storage of proxy credentials.
*/
public interface ProxyAuth
{
/** Credentials for a proxy server. */
public static class Credentials {
public final String username;
public final String password;
public Credentials (String username, String password) {
this.username = username;
this.password = password;
}
}
/**
* Loads the credentials for the app installed in {@code appDir}.
*/
public Credentials loadCredentials (String appDir);
/**
* Encrypts and saves the credentials for the app installed in {@code appDir}.
*/
public void saveCredentials (String appDir, String username, String password);
}
@@ -7,11 +7,12 @@ package com.threerings.getdown.util;
import java.io.IOException; import java.io.IOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.net.URLDecoder; import java.net.URLDecoder;
import com.threerings.getdown.data.SysProps; import com.threerings.getdown.data.SysProps;
import com.threerings.getdown.util.Base64;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
@@ -19,6 +20,7 @@ public class ConnectionUtil
{ {
/** /**
* Opens a connection to a URL, setting the authentication header if user info is present. * 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 url the URL to which to open a connection.
* @param connectTimeout if {@code > 0} then a timeout, in seconds, to use when opening the * @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 * connection. If {@code 0} is supplied, the connection timeout specified via system properties
@@ -27,10 +29,10 @@ public class ConnectionUtil
* the connection. If {@code 0} is supplied, the read timeout specified via system properties * the connection. If {@code 0} is supplied, the read timeout specified via system properties
* will be used instead. * will be used instead.
*/ */
public static URLConnection open (URL url, int connectTimeout, int readTimeout) public static URLConnection open (Proxy proxy, URL url, int connectTimeout, int readTimeout)
throws IOException throws IOException
{ {
URLConnection conn = url.openConnection(); URLConnection conn = url.openConnection(proxy);
// configure a connect timeout, if requested // configure a connect timeout, if requested
int ctimeout = connectTimeout > 0 ? connectTimeout : SysProps.connectTimeout(); int ctimeout = connectTimeout > 0 ? connectTimeout : SysProps.connectTimeout();
@@ -63,9 +65,9 @@ public class ConnectionUtil
* present. Throws a class cast exception if the connection returned is not the right type. See * present. Throws a class cast exception if the connection returned is not the right type. See
* {@link #open} for parameter documentation. * {@link #open} for parameter documentation.
*/ */
public static HttpURLConnection openHttp (URL url, int connectTimeout, int readTimeout) public static HttpURLConnection openHttp (
throws IOException Proxy proxy, URL url, int connectTimeout, int readTimeout) throws IOException
{ {
return (HttpURLConnection)open(url, connectTimeout, readTimeout); return (HttpURLConnection)open(proxy, url, connectTimeout, readTimeout);
} }
} }
@@ -13,6 +13,16 @@ import java.awt.GraphicsEnvironment;
import java.awt.Image; import java.awt.Image;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
@@ -20,29 +30,9 @@ import javax.swing.JButton;
import javax.swing.JFrame; import javax.swing.JFrame;
import javax.swing.JLayeredPane; import javax.swing.JLayeredPane;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import ca.beq.util.win32.registry.RegistryKey;
import ca.beq.util.win32.registry.RegistryValue;
import ca.beq.util.win32.registry.RootKey;
import com.samskivert.swing.util.SwingUtil; import com.samskivert.swing.util.SwingUtil;
import com.threerings.getdown.data.Application.UpdateInterface.Step;
import com.threerings.getdown.data.*; import com.threerings.getdown.data.*;
import com.threerings.getdown.data.Application.UpdateInterface.Step;
import com.threerings.getdown.net.Downloader; import com.threerings.getdown.net.Downloader;
import com.threerings.getdown.net.HTTPDownloader; import com.threerings.getdown.net.HTTPDownloader;
import com.threerings.getdown.tools.Patcher; import com.threerings.getdown.tools.Patcher;
@@ -186,24 +176,12 @@ public abstract class Getdown extends Thread
/** /**
* Configures our proxy settings (called by {@link ProxyPanel}) and fires up the launcher. * Configures our proxy settings (called by {@link ProxyPanel}) and fires up the launcher.
*/ */
public void configureProxy (String host, String port) public void configProxy (String host, String port, String username, String password)
{ {
log.info("User configured proxy", "host", host, "port", port); log.info("User configured proxy", "host", host, "port", port);
// if we're provided with valid values, create a proxy.txt file
if (!StringUtil.isBlank(host)) { if (!StringUtil.isBlank(host)) {
File pfile = _app.getLocalPath("proxy.txt"); ProxyUtil.configProxy(_app, host, port, username, password);
try (PrintStream pout = new PrintStream(new FileOutputStream(pfile))) {
pout.println("host = " + host);
if (!StringUtil.isBlank(port)) {
pout.println("port = " + port);
}
} catch (IOException ioe) {
log.warning("Error creating proxy file '" + pfile + "': " + ioe);
}
// also configure them in the JVM
setProxyProperties(host, port);
} }
// clear out our UI // clear out our UI
@@ -214,67 +192,11 @@ public abstract class Getdown extends Thread
new Thread(this).start(); new Thread(this).start();
} }
/** protected boolean detectProxy () {
* Reads and/or autodetects our proxy settings. if (ProxyUtil.autoDetectProxy(_app)) {
*
* @return true if we should proceed with running the launcher, false if we need to wait for
* the user to enter proxy settings.
*/
protected boolean detectProxy ()
{
// we may already have a proxy configured
if (System.getProperty("http.proxyHost") != null ||
System.getProperty("https.proxyHost") != null) {
return true; return true;
} }
// look in the Vinders registry
if (LaunchUtil.isWindows()) {
try {
String host = null, port = null;
boolean enabled = false;
RegistryKey.initialize();
RegistryKey r = new RegistryKey(RootKey.HKEY_CURRENT_USER, PROXY_REGISTRY);
for (Iterator<?> iter = r.values(); iter.hasNext(); ) {
RegistryValue value = (RegistryValue)iter.next();
if (value.getName().equals("ProxyEnable")) {
enabled = value.getStringValue().equals("1");
}
if (value.getName().equals("ProxyServer")) {
String strval = value.getStringValue();
int cidx = strval.indexOf(":");
if (cidx != -1) {
port = strval.substring(cidx+1);
strval = strval.substring(0, cidx);
}
host = strval;
}
}
if (enabled) {
setProxyProperties(host, port);
return true;
} else {
log.info("Detected no proxy settings in the registry.");
}
} catch (Throwable t) {
log.info("Failed to find proxy settings in Windows registry", "error", t);
}
}
// otherwise look for and read our proxy.txt file
File pfile = _app.getLocalPath("proxy.txt");
if (pfile.exists()) {
try {
Config pconf = Config.parseConfig(pfile, Config.createOpts(false));
setProxyProperties(pconf.getString("host"), pconf.getString("port"));
return true;
} catch (IOException ioe) {
log.warning("Failed to read '" + pfile + "': " + ioe);
}
}
// otherwise see if we actually need a proxy; first we have to initialize our application // otherwise 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 // to get some sort of interface configuration and the appbase URL
log.info("Checking whether we need to use a proxy..."); log.info("Checking whether we need to use a proxy...");
@@ -284,59 +206,15 @@ public abstract class Getdown extends Thread
// no worries // no worries
} }
updateStatus("m.detecting_proxy"); updateStatus("m.detecting_proxy");
if (!ProxyUtil.canLoadWithoutProxy(_app.getConfigResource().getRemote())) {
URL rurl = _app.getConfigResource().getRemote(); return false;
try {
// try to make a HEAD request for this URL (use short connect and read timeouts)
URLConnection conn = ConnectionUtil.open(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
if (hcon.getResponseCode() != HttpURLConnection.HTTP_OK) {
log.warning("Got a non-200 response but assuming we're OK because we got " +
"something...", "url", rurl, "rsp", hcon.getResponseCode());
}
} finally {
hcon.disconnect();
}
}
// we got through, so we appear not to require a proxy; make a blank proxy config and
// get on gettin' down
log.info("No proxy appears to be needed.");
try {
pfile.createNewFile();
} catch (IOException ioe) {
log.warning("Failed to create blank proxy file '" + pfile + "': " + ioe);
}
return true;
} catch (IOException ioe) {
log.info("Failed to HEAD " + rurl + ": " + ioe);
log.info("We probably need a proxy, but auto-detection failed.");
} }
// let the caller know that we need a proxy but can't detect it // we got through, so we appear not to require a proxy; make a blank proxy config so that
return false; // 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);
/** return true;
* Configures the JVM proxy system properties.
*/
protected void setProxyProperties (String host, String port)
{
if (!StringUtil.isBlank(host)) {
System.setProperty("http.proxyHost", host);
System.setProperty("https.proxyHost", host);
if (!StringUtil.isBlank(port)) {
System.setProperty("http.proxyPort", port);
System.setProperty("https.proxyPort", port);
}
log.info("Using proxy", "host", host, "port", port);
}
} }
protected void readConfig (boolean preloads) throws IOException { protected void readConfig (boolean preloads) throws IOException {
@@ -374,10 +252,6 @@ public abstract class Getdown extends Thread
*/ */
protected void getdown () protected void getdown ()
{ {
log.info("---------------- Proxy Info -----------------");
log.info("-- Proxy Host: " + System.getProperty("http.proxyHost"));
log.info("-- Proxy Port: " + System.getProperty("http.proxyPort"));
log.info("---------------------------------------------");
try { try {
// first parses our application deployment file // first parses our application deployment file
@@ -723,7 +597,7 @@ public abstract class Getdown extends Thread
// create our user interface // create our user interface
createInterfaceAsync(false); createInterfaceAsync(false);
Downloader dl = new HTTPDownloader() { Downloader dl = new HTTPDownloader(_app.proxy) {
@Override protected void resolvingDownloads () { @Override protected void resolvingDownloads () {
updateStatus("m.resolving"); updateStatus("m.resolving");
} }
@@ -1113,7 +987,7 @@ public abstract class Getdown extends Thread
@Override @Override
public void run () { public void run () {
try { try {
HttpURLConnection ucon = ConnectionUtil.openHttp(_url, 0, 0); HttpURLConnection ucon = ConnectionUtil.openHttp(_app.proxy, _url, 0, 0);
// if we have a tracking cookie configured, configure the request with it // if we have a tracking cookie configured, configure the request with it
if (_app.getTrackingCookieName() != null && if (_app.getTrackingCookieName() != null &&
@@ -1182,6 +1056,4 @@ public abstract class Getdown extends Thread
protected static final int MAX_LOOPS = 5; protected static final int MAX_LOOPS = 5;
protected static final long FALLBACK_CHECK_TIME = 1000L; protected static final long FALLBACK_CHECK_TIME = 1000L;
protected static final String PROXY_REGISTRY =
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
} }
@@ -7,22 +7,25 @@ package com.threerings.getdown.launcher;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.MissingResourceException; import java.util.MissingResourceException;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField; import javax.swing.JTextField;
import com.samskivert.swing.GroupLayout; import com.samskivert.swing.GroupLayout;
import com.samskivert.swing.Spacer; import com.samskivert.swing.Spacer;
import com.samskivert.swing.VGroupLayout; import com.samskivert.swing.VGroupLayout;
import com.threerings.getdown.util.MessageUtil; import com.threerings.getdown.util.MessageUtil;
import static com.threerings.getdown.Log.log; import static com.threerings.getdown.Log.log;
@@ -30,8 +33,7 @@ import static com.threerings.getdown.Log.log;
* Displays an interface with which the user can configure their proxy * Displays an interface with which the user can configure their proxy
* settings. * settings.
*/ */
public final class ProxyPanel extends JPanel public final class ProxyPanel extends JPanel implements ActionListener
implements ActionListener
{ {
public ProxyPanel (Getdown getdown, ResourceBundle msgs) public ProxyPanel (Getdown getdown, ResourceBundle msgs)
{ {
@@ -40,21 +42,50 @@ public final class ProxyPanel extends JPanel
setLayout(new VGroupLayout()); setLayout(new VGroupLayout());
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
add(new JLabel(get("m.configure_proxy"))); add(new SaneLabelField(get("m.configure_proxy")));
add(new Spacer(5, 5)); add(new Spacer(5, 5));
JPanel row = new JPanel(new BorderLayout(5, 5)); JPanel row = new JPanel(new GridLayout());
row.add(new JLabel(get("m.proxy_host")), BorderLayout.WEST); row.add(new SaneLabelField(get("m.proxy_host")), BorderLayout.WEST);
row.add(_host = new SaneTextField()); row.add(_host = new SaneTextField());
add(row); add(row);
row = new JPanel(new BorderLayout(5, 5)); row = new JPanel(new GridLayout());
row.add(new JLabel(get("m.proxy_port")), BorderLayout.WEST); row.add(new SaneLabelField(get("m.proxy_port")), BorderLayout.WEST);
row.add(_port = new SaneTextField()); row.add(_port = new SaneTextField());
add(row); add(row);
add(new Spacer(5, 5)); add(new Spacer(5, 5));
add(new JLabel(get("m.proxy_extra")));
row = new JPanel(new GridLayout());
row.add(new SaneLabelField(get("m.proxy_auth_required")), BorderLayout.WEST);
_useAuth = new JCheckBox();
row.add(_useAuth);
add(row);
row = new JPanel(new GridLayout());
row.add(new SaneLabelField(get("m.proxy_username")), BorderLayout.WEST);
_username = new SaneTextField();
_username.setEnabled(false);
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);
row.add(_password);
add(row);
_useAuth.addItemListener(new ItemListener() {
@Override public void itemStateChanged (ItemEvent event) {
boolean selected = (event.getStateChange() == ItemEvent.SELECTED);
_username.setEnabled(selected);
_password.setEnabled(selected);
}
});
add(new Spacer(5, 5));
row = GroupLayout.makeButtonBox(GroupLayout.CENTER); row = GroupLayout.makeButtonBox(GroupLayout.CENTER);
JButton button; JButton button;
@@ -79,7 +110,7 @@ public final class ProxyPanel extends JPanel
// documentation inherited // documentation inherited
@Override @Override
public void addNotify () public void addNotify ()
{ {
super.addNotify(); super.addNotify();
_host.requestFocusInWindow(); _host.requestFocusInWindow();
@@ -93,17 +124,23 @@ public final class ProxyPanel extends JPanel
// or the JLabel will claim a bogus height thinking it can lay its // or the JLabel will claim a bogus height thinking it can lay its
// text out all on one line which will booch the whole UI's // text out all on one line which will booch the whole UI's
// preferred size // preferred size
return new Dimension(500, 350); return new Dimension(500, 320);
} }
// documentation inherited from interface // documentation inherited from interface
@Override
public void actionPerformed (ActionEvent e) public void actionPerformed (ActionEvent e)
{ {
String cmd = e.getActionCommand(); String cmd = e.getActionCommand();
if (cmd.equals("ok")) { if (cmd.equals("ok")) {
// communicate this info back to getdown String user = null, pass = null;
_getdown.configureProxy(_host.getText(), _port.getText()); if (_useAuth.isSelected()) {
user = _username.getText();
// we have to keep the proxy password around for every HTTP request, so having it
// in a char[] that gets zeroed out after use is not viable for this use case
pass = new String(_password.getPassword());
}
_getdown.configProxy(_host.getText(), _port.getText(), user, pass);
} else { } else {
// they canceled, we're outta here // they canceled, we're outta here
System.exit(0); System.exit(0);
@@ -126,19 +163,34 @@ public final class ProxyPanel extends JPanel
} }
} }
protected static class SaneTextField extends JTextField protected static class SaneLabelField extends JLabel {
{ public SaneLabelField(String message) { super(message); }
@Override @Override public Dimension getPreferredSize () {
public Dimension getPreferredSize () { return clampWidth(super.getPreferredSize(), 200);
Dimension d = super.getPreferredSize();
d.width = Math.max(d.width, 150);
return d;
} }
} }
protected static class SaneTextField extends JTextField {
@Override public Dimension getPreferredSize () {
return clampWidth(super.getPreferredSize(), 150);
}
}
protected static class SanePasswordField extends JPasswordField {
@Override public Dimension getPreferredSize () {
return clampWidth(super.getPreferredSize(), 150);
}
}
protected static Dimension clampWidth (Dimension dim, int minWidth) {
dim.width = Math.max(dim.width, minWidth);
return dim;
}
protected Getdown _getdown; protected Getdown _getdown;
protected ResourceBundle _msgs; protected ResourceBundle _msgs;
protected JTextField _host; protected JTextField _host;
protected JTextField _port; protected JTextField _port;
protected JCheckBox _useAuth;
protected JTextField _username;
protected JPasswordField _password;
} }
@@ -0,0 +1,210 @@
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2018 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.launcher;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.util.Iterator;
import java.util.ServiceLoader;
import ca.beq.util.win32.registry.RegistryKey;
import ca.beq.util.win32.registry.RegistryValue;
import ca.beq.util.win32.registry.RootKey;
import com.threerings.getdown.data.Application;
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;
import static com.threerings.getdown.Log.log;
public class ProxyUtil {
public static boolean autoDetectProxy (Application app)
{
String host = null, port = null;
// check for a proxy configured via system properties
if (System.getProperty("https.proxyHost") != null) {
host = System.getProperty("https.proxyHost");
port = System.getProperty("https.proxyPort");
}
if (StringUtil.isBlank(host) && System.getProperty("http.proxyHost") != null) {
host = System.getProperty("http.proxyHost");
port = System.getProperty("http.proxyPort");
}
// check the Windows registry
if (StringUtil.isBlank(host) && LaunchUtil.isWindows()) {
try {
String rhost = null, rport = null;
boolean enabled = false;
RegistryKey.initialize();
RegistryKey r = new RegistryKey(RootKey.HKEY_CURRENT_USER, PROXY_REGISTRY);
for (Iterator<?> iter = r.values(); iter.hasNext(); ) {
RegistryValue value = (RegistryValue)iter.next();
if (value.getName().equals("ProxyEnable")) {
enabled = value.getStringValue().equals("1");
}
if (value.getName().equals("ProxyServer")) {
String strval = value.getStringValue();
int cidx = strval.indexOf(":");
if (cidx != -1) {
rport = strval.substring(cidx+1);
strval = strval.substring(0, cidx);
}
rhost = strval;
}
}
if (enabled) {
host = rhost;
port = rport;
} else {
log.info("Detected no proxy settings in the registry.");
}
} catch (Throwable t) {
log.info("Failed to find proxy settings in Windows registry", "error", t);
}
}
// look for a proxy.txt file
if (StringUtil.isBlank(host)) {
String[] hostPort = loadProxy(app);
host = hostPort[0];
port = hostPort[1];
}
if (StringUtil.isBlank(host)) {
return false;
}
// yay, we found a proxy configuration, configure it in the app
initProxy(app, host, port, null, null);
return true;
}
public static boolean canLoadWithoutProxy (URL rurl)
{
log.info("Testing whether proxy is needed, via: " + 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
return true;
}
} catch (IOException ioe) {
log.info("Failed to HEAD " + rurl + ": " + ioe);
log.info("We probably need a proxy, but auto-detection failed.");
}
return false;
}
public static void configProxy (Application app, String host, String port,
String username, String password) {
// save our proxy host and port in a local file
saveProxy(app, host, port);
// save our credentials via the SPI
if (!StringUtil.isBlank(username) && !StringUtil.isBlank(password)) {
ServiceLoader<ProxyAuth> loader = ServiceLoader.load(ProxyAuth.class);
Iterator<ProxyAuth> iterator = loader.iterator();
String appDir = app.getAppDir().getAbsolutePath();
while (iterator.hasNext()) {
iterator.next().saveCredentials(appDir, username, password);
}
}
// also configure them in the app
initProxy(app, host, port, username, password);
}
public static String[] loadProxy (Application app) {
File pfile = app.getLocalPath("proxy.txt");
if (pfile.exists()) {
try {
Config pconf = Config.parseConfig(pfile, Config.createOpts(false));
return new String[] { pconf.getString("host"), pconf.getString("port") };
} catch (IOException ioe) {
log.warning("Failed to read '" + pfile + "': " + ioe);
}
}
return new String[] { null, null};
}
public static void saveProxy (Application app, String host, String port) {
File pfile = app.getLocalPath("proxy.txt");
try (PrintStream pout = new PrintStream(new FileOutputStream(pfile))) {
if (!StringUtil.isBlank(host)) {
pout.println("host = " + host);
}
if (!StringUtil.isBlank(port)) {
pout.println("port = " + port);
}
} catch (IOException ioe) {
log.warning("Error creating proxy file '" + pfile + "': " + ioe);
}
}
public static void initProxy (Application app, String host, String port,
String username, String password)
{
// check whether we have saved proxy credentials
String appDir = app.getAppDir().getAbsolutePath();
ServiceLoader<ProxyAuth> loader = ServiceLoader.load(ProxyAuth.class);
Iterator<ProxyAuth> iter = loader.iterator();
ProxyAuth.Credentials creds = iter.hasNext() ? iter.next().loadCredentials(appDir) : null;
if (creds != null) {
username = creds.username;
password = creds.password;
}
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 (haveCreds) {
final String fuser = username;
final char[] fpass = password.toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override protected PasswordAuthentication getPasswordAuthentication () {
return new PasswordAuthentication(fuser, fpass);
}
});
}
}
protected static final String PROXY_REGISTRY =
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
}
@@ -11,15 +11,11 @@ m.abort_cancel = Continue installation
m.detecting_proxy = Trying to auto-detect proxy settings m.detecting_proxy = Trying to auto-detect proxy settings
m.configure_proxy = <html>We were unable to connect to our servers to download data. \ m.configure_proxy = <html>We were unable to connect to the application server to download data. \
<ul><li> If Windows Firewall or Norton Internet Security was instructed \ <p> Please make sure that no virus scanner or firewall is blocking network communicaton with \
to block <code>javaw.exe</code> we cannot download the data. You will need \ the server. \
to allow <code>javaw.exe</code> to access the internet. You can try \ <p> Your computer may access the Internet through a proxy and we were unable to automatically \
running the app again, but you may need to clear javaw.exe from your \ detect your proxy settings. If you know your proxy settings, you can enter them below.</html>
firewall configuration ( Start -> Control Panel -> Windows Firewall ).</ul> \
<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.proxy_extra = <html>If you are sure that you don't use a proxy then \ 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 \ perhaps there is a temporary Internet outage that is preventing us from \
@@ -28,6 +24,9 @@ m.proxy_extra = <html>If you are sure that you don't use a proxy then \
m.proxy_host = Proxy IP m.proxy_host = Proxy IP
m.proxy_port = Proxy port m.proxy_port = Proxy port
m.proxy_username = Username
m.proxy_password = Password
m.proxy_auth_required = Authentication required
m.proxy_ok = OK m.proxy_ok = OK
m.proxy_cancel = Cancel m.proxy_cancel = Cancel
@@ -13,27 +13,21 @@ m.abort_cancel = Installation fortsetzen
m.detecting_proxy = Versuche Proxy-Einstellungen automatisch zu ermitteln m.detecting_proxy = Versuche Proxy-Einstellungen automatisch zu ermitteln
m.configure_proxy = <html>Es war nicht m\u00f6glich, eine Verbindung zu unserem \ m.configure_proxy = <html>Es konnte keine Verbindung zum Applikations-Server aufgebaut werden. \
Server herzustellen, um die Anwendungsdaten herunterzuladen. \ <p>Bitte kontrollieren Sie die Proxyeinstellungen und stellen Sie sicher, dass keine lokal oder \
<ul><li> Falls Windows Firewall oder Norton Internet Security angewiesen \ im Netzwerk betriebene Sicherheitsanwendung (Virenscanner, Firewall, etc.) die Kommunikation \
wurden, <code>javaw.exe</code> zu blockieren, kann die Anwendung nicht \ mit dem Server blockiert.<br> \
heruntergeladen werden. Du musst <code>javaw.exe</code> erlauben, \ Wenn kein Proxy verwendet werden soll, l\u00f6schen Sie bitte alle Eintr\u00e4ge in den unten \
Verbindung zum Internet herzustellen. Du kannst nochmals versuchen, die \ stehenden Feldern und klicken sie auf OK.</html>
Anwendung zu starten, aber eventuell musst du javaw.exe aus der \
Firewall-Konfiguration entfernen (Start -> Systemsteuerung -> Windows \
Firewall). </ul> \
<p> Eventuell ist der Computer durch einen Proxy mit dem Internet \
verbunden. Es war uns nicht m\u00f6glich, die Proxy-Einstellungen automatisch \
zu ermitteln. Falls bekannt, k\u00f6nnen diese unten eingetragen \
werden.</html>
m.proxy_extra = <html>Falls kein Proxy verwendet wird, liegt \ m.proxy_extra = <html>Sollten Sie keine Proxyeinstellungen gesetzt haben wenden Sie sich bitte \
m\u00f6glicherweise eine St\u00f6rung in der Internetverbindung zu unserem Server \ an Ihren Administrator.</html>
vor. In diesem Fall bitte abbrechen und sp\u00e4ter noch einmal \
versuchen.</html>
m.proxy_host = Proxy IP m.proxy_host = Proxy-Adresse
m.proxy_port = Proxy port m.proxy_port = Proxy-Port
m.proxy_username = Benutzername
m.proxy_password = Passwort
m.proxy_auth_required = Authentisierung erforderlich
m.proxy_ok = OK m.proxy_ok = OK
m.proxy_cancel = Abbrechen m.proxy_cancel = Abbrechen
@@ -29,6 +29,9 @@ m.proxy_extra = <html>Si est\u00e1s seguro de que no tienes un proxy entonces \
m.proxy_host = IP proxy m.proxy_host = IP proxy
m.proxy_port = Puerto proxy m.proxy_port = Puerto proxy
m.proxy_username = Nombre de usuario
m.proxy_password = Contrase\u00f1a
m.proxy_auth_required = Autenticacion requerida
m.proxy_ok = OK m.proxy_ok = OK
m.proxy_cancel = Cancelar m.proxy_cancel = Cancelar
@@ -27,6 +27,9 @@ m.proxy_extra =<html>Si vous \u00eates certain de ne pas utiliser de proxy, il e
m.proxy_host = Proxy IP m.proxy_host = Proxy IP
m.proxy_port = Proxy port m.proxy_port = Proxy port
m.proxy_username = Nom d'utilisateur
m.proxy_password = Mot de passe
m.proxy_auth_required = Identification requise
m.proxy_ok = OK m.proxy_ok = OK
m.proxy_cancel = Annuler m.proxy_cancel = Annuler
@@ -28,6 +28,9 @@ m.proxy_extra = <html>Se sei sicuro di non usare proxy \
m.proxy_host = IP Proxy m.proxy_host = IP Proxy
m.proxy_port = Porta Proxy m.proxy_port = Porta Proxy
m.proxy_username = Nome utente
m.proxy_password = Parola d'ordine
m.proxy_auth_required = Autenticazione richiesta
m.proxy_ok = OK m.proxy_ok = OK
m.proxy_cancel = Annulla m.proxy_cancel = Annulla
@@ -29,6 +29,9 @@ m.proxy_extra = <html>\u30d7\u30ed\u30ad\u30b7\u3092\u4f7f\u7528\u3057\u3066\u30
m.proxy_host = \u30d7\u30ed\u30ad\u30b7IP m.proxy_host = \u30d7\u30ed\u30ad\u30b7IP
m.proxy_port = \u30d7\u30ed\u30ad\u30b7\u30dd\u30fc\u30c8 m.proxy_port = \u30d7\u30ed\u30ad\u30b7\u30dd\u30fc\u30c8
m.proxy_username = Username
m.proxy_password = Password
m.proxy_auth_required = Authentication required
m.proxy_ok = OK m.proxy_ok = OK
m.proxy_cancel = \u30ad\u30e3\u30f3\u30bb\u30eb m.proxy_cancel = \u30ad\u30e3\u30f3\u30bb\u30eb
@@ -24,6 +24,9 @@ m.proxy_extra = \uC790\uB3D9 \uD504\uB85D\uC2DC\uB97C \uC124\uC815\uC744 \uC2DC\
m.proxy_host = \uD504\uB85D\uC2DC IP m.proxy_host = \uD504\uB85D\uC2DC IP
m.proxy_port = \uD504\uB85D\uC2DC \uD3EC\uD2B8 m.proxy_port = \uD504\uB85D\uC2DC \uD3EC\uD2B8
m.proxy_username = Username
m.proxy_password = Password
m.proxy_auth_required = Authentication required
m.proxy_ok = OK m.proxy_ok = OK
m.proxy_cancel = \uCDE8\uC18C m.proxy_cancel = \uCDE8\uC18C
@@ -31,6 +31,9 @@ m.proxy_extra = <html>Se voc\u00EA tem certeza que n\u00E3o usa um proxy, ent\u0
m.proxy_host = IP do Proxy m.proxy_host = IP do Proxy
m.proxy_port = Porta do Proxy m.proxy_port = Porta do Proxy
m.proxy_username = Nome de usu\u00e1rio
m.proxy_password = Senha
m.proxy_auth_required = Autentifica\u00e7\u00e3o requerida
m.proxy_ok = OK m.proxy_ok = OK
m.proxy_cancel = Cancelar m.proxy_cancel = Cancelar
@@ -16,6 +16,9 @@ m.proxy_extra = <html>\u5982\u679c\u60a8\u786e\u5b9a\u60a8\u6ca1\u6709\u4f7f\u75
m.proxy_host = \u4ee3\u7406\u670d\u52a1\u5668\u7684IP\u5730\u5740 m.proxy_host = \u4ee3\u7406\u670d\u52a1\u5668\u7684IP\u5730\u5740
m.proxy_port = \u4ee3\u7406\u670d\u52a1\u5668\u7684\u7aef\u53e3\u53f7 m.proxy_port = \u4ee3\u7406\u670d\u52a1\u5668\u7684\u7aef\u53e3\u53f7
m.proxy_username = Username
m.proxy_password = Password
m.proxy_auth_required = Authentication required
m.proxy_ok = \u786e\u5b9a m.proxy_ok = \u786e\u5b9a
m.proxy_cancel = \u53d6\u6d88 m.proxy_cancel = \u53d6\u6d88