Merge branch 'master' of https://github.com/threerings/getdown
# Conflicts: # core/src/main/java/com/threerings/getdown/data/Application.java
This commit is contained in:
@@ -5,6 +5,19 @@
|
||||
* Fixed issues with proxy information not getting properly passed through to app.
|
||||
Via [#216](//github.com/threerings/getdown/pull/216).
|
||||
|
||||
* `appbase` and `latest` properties in `getdown.txt` now process env var subtitutions.
|
||||
|
||||
* 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,16 +7,13 @@ package com.threerings.getdown.tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.Task;
|
||||
|
||||
import com.threerings.getdown.data.Digest;
|
||||
|
||||
/**
|
||||
* An ant task used to create a <code>digest.txt</code> for a Getdown
|
||||
* An ant task used to create a {@code digest.txt} for a Getdown
|
||||
* application deployment.
|
||||
*/
|
||||
public class DigesterTask extends Task
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
|
||||
package com.threerings.getdown.tests;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import com.threerings.getdown.tools.Digester;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class DigesterIT {
|
||||
|
||||
@@ -32,23 +32,23 @@ public class DigesterIT {
|
||||
Files.delete(digest2);
|
||||
|
||||
assertEquals(Arrays.asList(
|
||||
"getdown.txt = 779c74fb4b251e18faf6e240a0667964",
|
||||
"getdown.txt = 9c9b2494929c99d44ae51034d59e1a1b",
|
||||
"testapp.jar = 404dafa55e78b25ec0e3a936357b1883",
|
||||
"funny%test dir/some=file.txt = d8e8fca2dc0f896fd7cb4cb0031ba249",
|
||||
"crazyhashfile#txt = f29d23fd5ab1781bd8d0760b3a516f16",
|
||||
"foo.jar = 46ca4cc9079d9d019bb30cd21ebbc1ec",
|
||||
"script.sh = f66e8ea25598e67e99c47d9b0b2a2cdf",
|
||||
"digest.txt = f5561d85e4d80cc85883963897e58ff6"
|
||||
"digest.txt = 11f9ba349cf9edacac4d72a3158447e5"
|
||||
), digestLines);
|
||||
|
||||
assertEquals(Arrays.asList(
|
||||
"getdown.txt = 4f0c657895c3c3a35fa55bf5951c64fa9b0694f8fc685af3f1d8635c639e066b",
|
||||
"getdown.txt = 1efecfae2a189002a6658f17d162b1922c7bde978944949276dc038a0df2461f",
|
||||
"testapp.jar = c9cb1906afbf48f8654b416c3f831046bd3752a76137e5bf0a9af2f790bf48e0",
|
||||
"funny%test dir/some=file.txt = f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2",
|
||||
"crazyhashfile#txt = 6816889f922de38f145db215a28ad7c5e1badf7354b5cdab225a27486789fa3b",
|
||||
"foo.jar = ea188b872e0496debcbe00aaadccccb12a8aa9b025bb62c130cd3d9b8540b062",
|
||||
"script.sh = cca1c5c7628d9bf7533f655a9cfa6573d64afb8375f81960d1d832dc5135c988",
|
||||
"digest2.txt = 70b442c9f56660561921da3368e1a206f05c379182fab3062750b7ddcf303407"
|
||||
"digest2.txt = 41eacdabda8909bdbbf61e4f980867f4003c16a12f6770e6fc619b6af100e05b"
|
||||
), digest2Lines);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,18 @@ apparg = %APPDIR%
|
||||
# test the %env% mechanism
|
||||
jvmarg = -Dusername=\%ENV.USER%
|
||||
|
||||
# test various java_*** configs, they are not interesting for digester
|
||||
java_local_dir = jre
|
||||
java_max_version = 1089999
|
||||
java_min_version = [windows] 1080111
|
||||
java_min_version = [!windows] 1080192
|
||||
java_exact_version_required = [linux] true
|
||||
java_location = [linux-amd64] /files/java/java_linux_64.zip
|
||||
java_location = [linux-i386] /files/java/java_linux_32.zip
|
||||
java_location = [mac] /files/java/java_mac_64.zip
|
||||
java_location = [windows-amd64] /files/java/java_windows_64.zip
|
||||
java_location = [windows-x86] /files/java/java_windows_32.zip
|
||||
|
||||
strict_comments = true
|
||||
resource = funny%test dir/some=file.txt
|
||||
resource = crazyhashfile#txt
|
||||
|
||||
@@ -15,7 +15,7 @@ import java.util.logging.*;
|
||||
/**
|
||||
* A placeholder class that contains a reference to the log object used by the Getdown code.
|
||||
*/
|
||||
public class Log
|
||||
public final class Log
|
||||
{
|
||||
public static class Shim {
|
||||
/**
|
||||
@@ -143,6 +143,5 @@ public class Log
|
||||
protected FieldPosition _fpos = new FieldPosition(SimpleDateFormat.DATE_FIELD);
|
||||
}
|
||||
|
||||
protected static final String DATE_FORMAT = "{0,date} {0,time}";
|
||||
protected static final Level[] LEVELS = {Level.FINE, Level.INFO, Level.WARNING, Level.SEVERE};
|
||||
}
|
||||
|
||||
@@ -96,6 +96,6 @@ public class GarbageCollector
|
||||
private static File getCachedFile (File file)
|
||||
{
|
||||
return !isLastAccessedFile(file) ? file : new File(
|
||||
file.getParentFile(), file.getName().substring(0, file.getName().lastIndexOf(".")));
|
||||
file.getParentFile(), file.getName().substring(0, file.getName().lastIndexOf('.')));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ public class ResourceCache
|
||||
|
||||
private String getFileSuffix (File fileToCache) {
|
||||
String fileName = fileToCache.getName();
|
||||
int index = fileName.lastIndexOf(".");
|
||||
int index = fileName.lastIndexOf('.');
|
||||
|
||||
return index > -1 ? fileName.substring(index) : "";
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -33,7 +32,7 @@ import static com.threerings.getdown.Log.log;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* Parses and provide access to the information contained in the <code>getdown.txt</code>
|
||||
* Parses and provide access to the information contained in the {@code getdown.txt}
|
||||
* configuration file.
|
||||
*/
|
||||
public class Application
|
||||
@@ -212,10 +211,10 @@ public class Application
|
||||
* Used by {@link #verifyMetadata} to communicate status in circumstances where it needs to
|
||||
* take network actions.
|
||||
*/
|
||||
public static interface StatusDisplay
|
||||
public interface StatusDisplay
|
||||
{
|
||||
/** Requests that the specified status message be displayed. */
|
||||
public void updateStatus (String message);
|
||||
void updateStatus (String message);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,19 +232,56 @@ 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
|
||||
/**
|
||||
* Reads the {@code getdown.txt} config file into a {@code Config} object and returns it.
|
||||
*/
|
||||
public static Config readConfig (EnvConfig envc, boolean checkPlatform) throws IOException {
|
||||
Config config = null;
|
||||
File cfgfile = new File(envc.appDir, CONFIG_FILE);
|
||||
Config.ParseOpts opts = Config.createOpts(checkPlatform);
|
||||
try {
|
||||
// if we have a configuration file, read the data from it
|
||||
if (cfgfile.exists()) {
|
||||
config = Config.parseConfig(cfgfile, opts);
|
||||
}
|
||||
// otherwise, try reading data from our backup config file; thanks to funny windows
|
||||
// bullshit, we have to do this backup file fiddling in case we got screwed while
|
||||
// updating getdown.txt during normal operation
|
||||
else if ((cfgfile = new File(envc.appDir, Application.CONFIG_FILE + "_old")).exists()) {
|
||||
config = Config.parseConfig(cfgfile, opts);
|
||||
}
|
||||
// otherwise, issue a warning that we found no getdown file
|
||||
else {
|
||||
log.info("Found no getdown.txt file", "appdir", envc.appDir);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warning("Failure reading config file", "file", config, e);
|
||||
}
|
||||
|
||||
// if we failed to read our config file, check for an appbase specified via a system
|
||||
// property; we can use that to bootstrap ourselves back into operation
|
||||
if (config == null) {
|
||||
log.info("Using 'appbase' from bootstrap config", "appbase", envc.appBase);
|
||||
Map<String, Object> cdata = new HashMap<>();
|
||||
cdata.put("appbase", envc.appBase);
|
||||
config = new Config(cdata);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/** 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</code>
|
||||
* Creates an application instance which records the location of the {@code getdown.txt}
|
||||
* configuration file from the supplied application directory.
|
||||
*
|
||||
*/
|
||||
public Application (EnvConfig envc) {
|
||||
_envc = envc;
|
||||
_config = new File(envc.appDir, CONFIG_FILE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -369,8 +405,7 @@ public class Application
|
||||
*/
|
||||
public List<Resource> getActiveCodeResources ()
|
||||
{
|
||||
ArrayList<Resource> codes = new ArrayList<>();
|
||||
codes.addAll(getCodeResources());
|
||||
List<Resource> codes = new ArrayList<>(getCodeResources());
|
||||
for (AuxGroup aux : getAuxGroups()) {
|
||||
if (isAuxGroupActive(aux.name)) {
|
||||
codes.addAll(aux.codes);
|
||||
@@ -398,8 +433,7 @@ public class Application
|
||||
*/
|
||||
public List<Resource> getActiveResources ()
|
||||
{
|
||||
ArrayList<Resource> rsrcs = new ArrayList<>();
|
||||
rsrcs.addAll(getResources());
|
||||
List<Resource> rsrcs = new ArrayList<>(getResources());
|
||||
for (AuxGroup aux : getAuxGroups()) {
|
||||
if (isAuxGroupActive(aux.name)) {
|
||||
rsrcs.addAll(aux.rsrcs);
|
||||
@@ -544,44 +578,22 @@ public class Application
|
||||
* @exception IOException thrown if there is an error reading the file or an error encountered
|
||||
* during its parsing.
|
||||
*/
|
||||
public Config init (boolean checkPlatform)
|
||||
throws IOException
|
||||
public Config init (boolean checkPlatform) throws IOException
|
||||
{
|
||||
Config config = null;
|
||||
File cfgfile = _config;
|
||||
Config.ParseOpts opts = Config.createOpts(checkPlatform);
|
||||
try {
|
||||
// if we have a configuration file, read the data from it
|
||||
if (cfgfile.exists()) {
|
||||
config = Config.parseConfig(_config, opts);
|
||||
}
|
||||
// otherwise, try reading data from our backup config file; thanks to funny windows
|
||||
// bullshit, we have to do this backup file fiddling in case we got screwed while
|
||||
// updating getdown.txt during normal operation
|
||||
else if ((cfgfile = getLocalPath(CONFIG_FILE + "_old")).exists()) {
|
||||
config = Config.parseConfig(cfgfile, opts);
|
||||
}
|
||||
// otherwise, issue a warning that we found no getdown file
|
||||
else {
|
||||
log.info("Found no getdown.txt file", "appdir", getAppDir());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warning("Failure reading config file", "file", config, e);
|
||||
}
|
||||
|
||||
// if we failed to read our config file, check for an appbase specified via a system
|
||||
// property; we can use that to bootstrap ourselves back into operation
|
||||
if (config == null) {
|
||||
String appbase = _envc.appBase;
|
||||
log.info("Using 'appbase' from bootstrap config", "appbase", appbase);
|
||||
Map<String, Object> cdata = new HashMap<>();
|
||||
cdata.put("appbase", appbase);
|
||||
config = new Config(cdata);
|
||||
}
|
||||
|
||||
// extract our version information
|
||||
_version = config.getLong("version", -1L);
|
||||
Config config = readConfig(_envc, checkPlatform);
|
||||
initBase(config);
|
||||
initJava(config);
|
||||
initTracking(config);
|
||||
initResources(config);
|
||||
initArgs(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the basic config info from {@code config} into this instance. This includes things
|
||||
* like the appbase and version.
|
||||
*/
|
||||
public void initBase (Config config) throws IOException {
|
||||
// first determine our application base, this way if anything goes wrong later in the
|
||||
// process, our caller can use the appbase to download a new configuration file
|
||||
_appbase = config.getString("appbase");
|
||||
@@ -589,25 +601,29 @@ public class Application
|
||||
throw new RuntimeException("m.missing_appbase");
|
||||
}
|
||||
|
||||
// check if we're overriding the domain in the appbase
|
||||
// check if we're overriding the domain in the appbase, and sub envvars
|
||||
_appbase = processArg(SysProps.overrideAppbase(_appbase));
|
||||
|
||||
// make sure there's a trailing slash
|
||||
if (!_appbase.endsWith("/")) {
|
||||
_appbase = _appbase + "/";
|
||||
_appbase += "/";
|
||||
}
|
||||
|
||||
// extract our version information
|
||||
_version = config.getLong("version", -1L);
|
||||
|
||||
// if we are a versioned deployment, create a versioned appbase
|
||||
try {
|
||||
_vappbase = createVAppBase(_version);
|
||||
} catch (MalformedURLException mue) {
|
||||
String err = MessageUtil.tcompose("m.invalid_appbase", _appbase);
|
||||
throw (IOException) new IOException(err).initCause(mue);
|
||||
throw new IOException(err, mue);
|
||||
}
|
||||
|
||||
// check for a latest config URL
|
||||
String latest = processArg(config.getString("latest"));
|
||||
String latest = config.getString("latest");
|
||||
if (latest != null) {
|
||||
latest = processArg(latest);
|
||||
if (latest.startsWith(_appbase)) {
|
||||
latest = _appbase + latest.substring(_appbase.length());
|
||||
} else {
|
||||
@@ -620,20 +636,28 @@ public class Application
|
||||
}
|
||||
}
|
||||
|
||||
String appPrefix = _envc.appId == null ? "" : (_envc.appId + ".");
|
||||
|
||||
// determine our application class name (use app-specific class _if_ one is provided)
|
||||
_class = config.getString("class");
|
||||
if (appPrefix.length() > 0) {
|
||||
_class = config.getString(appPrefix + "class", _class);
|
||||
}
|
||||
if (_class == null) {
|
||||
throw new IOException("m.missing_class");
|
||||
}
|
||||
|
||||
// determine whether we want strict comments
|
||||
_strictComments = config.getBoolean("strict_comments");
|
||||
|
||||
// determine whether we want to allow offline operation (defaults to false)
|
||||
_allowOffline = config.getBoolean("allow_offline");
|
||||
|
||||
// whether to cache code resources and launch from cache
|
||||
_useCodeCache = config.getBoolean("use_code_cache");
|
||||
_codeCacheRetentionDays = config.getInt("code_cache_retention_days", 7);
|
||||
|
||||
// maximum simultaneous downloads
|
||||
_maxConcDownloads = Math.max(1, config.getInt("max_concurrent_downloads",
|
||||
SysProps.threadPoolSize()));
|
||||
|
||||
_verifyTimeout = config.getInt("verify_timeout", 60);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the JVM requirements from {@code config} into this instance. This includes things like
|
||||
* the min and max java version, location of a locally installed JRE, etc.
|
||||
*/
|
||||
public void initJava (Config config) {
|
||||
// check to see if we're using a custom java.version property and regex
|
||||
_javaVersionProp = config.getString("java_version_prop", _javaVersionProp);
|
||||
_javaVersionRegex = config.getString("java_version_regex", _javaVersionRegex);
|
||||
@@ -648,17 +672,16 @@ public class Application
|
||||
// check to see if we require a particular JVM version and have a supplied JVM
|
||||
_javaExactVersionRequired = config.getBoolean("java_exact_version_required");
|
||||
|
||||
// this is a little weird, but when we're run from the digester, we see a String[] which
|
||||
// contains java locations for all platforms which we can't grok, but the digester doesn't
|
||||
// need to know about that; when we're run in a real application there will be only one!
|
||||
Object javaloc = config.getRaw("java_location");
|
||||
if (javaloc instanceof String) {
|
||||
_javaLocation = (String)javaloc;
|
||||
}
|
||||
_javaLocation = config.getString("java_location");
|
||||
|
||||
// used only in conjunction with java_location
|
||||
_javaLocalDir = getLocalPath(config.getString("java_local_dir", LaunchUtil.LOCAL_JAVA_DIR));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the install tracking info from {@code config} into this instance.
|
||||
*/
|
||||
public void initTracking (Config config) {
|
||||
// determine whether we have any tracking configuration
|
||||
_trackingURL = config.getString("tracking_url");
|
||||
|
||||
@@ -683,14 +706,16 @@ public class Application
|
||||
|
||||
// Some app may need to generate google analytics code
|
||||
_trackingGAHash = config.getString("tracking_ga_hash");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the app resource info from {@code config} into this instance.
|
||||
*/
|
||||
public void initResources (Config config) throws IOException {
|
||||
// clear our arrays as we may be reinitializing
|
||||
_codes.clear();
|
||||
_resources.clear();
|
||||
_auxgroups.clear();
|
||||
_jvmargs.clear();
|
||||
_appargs.clear();
|
||||
_txtJvmArgs.clear();
|
||||
|
||||
// parse our code resources
|
||||
if (config.getMultiValue("code") == null &&
|
||||
@@ -709,10 +734,10 @@ public class Application
|
||||
|
||||
// parse our auxiliary resource groups
|
||||
for (String auxgroup : config.getList("auxgroups")) {
|
||||
ArrayList<Resource> codes = new ArrayList<>();
|
||||
List<Resource> codes = new ArrayList<>();
|
||||
parseResources(config, auxgroup + ".code", Resource.NORMAL, codes);
|
||||
parseResources(config, auxgroup + ".ucode", Resource.UNPACK, codes);
|
||||
ArrayList<Resource> rsrcs = new ArrayList<>();
|
||||
List<Resource> rsrcs = new ArrayList<>();
|
||||
parseResources(config, auxgroup + ".resource", Resource.NORMAL, rsrcs);
|
||||
parseResources(config, auxgroup + ".xresource", Resource.EXEC, rsrcs);
|
||||
parseResources(config, auxgroup + ".uresource", Resource.UNPACK, rsrcs);
|
||||
@@ -720,21 +745,38 @@ public class Application
|
||||
parseResources(config, auxgroup + ".nresource", Resource.NATIVE, rsrcs);
|
||||
_auxgroups.put(auxgroup, new AuxGroup(auxgroup, codes, rsrcs));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the command line arg info from {@code config} into this instance.
|
||||
*/
|
||||
public void initArgs (Config config) throws IOException {
|
||||
_jvmargs.clear();
|
||||
_appargs.clear();
|
||||
_txtJvmArgs.clear();
|
||||
|
||||
String appPrefix = _envc.appId == null ? "" : (_envc.appId + ".");
|
||||
|
||||
// determine our application class name (use app-specific class _if_ one is provided)
|
||||
_class = config.getString("class");
|
||||
if (appPrefix.length() > 0) {
|
||||
_class = config.getString(appPrefix + "class", _class);
|
||||
}
|
||||
if (_class == null) {
|
||||
throw new IOException("m.missing_class");
|
||||
}
|
||||
|
||||
// transfer our JVM arguments (we include both "global" args and app_id-prefixed args)
|
||||
String[] jvmargs = config.getMultiValue("jvmarg");
|
||||
addAll(jvmargs, _jvmargs);
|
||||
addAll(config.getMultiValue("jvmarg"), _jvmargs);
|
||||
if (appPrefix.length() > 0) {
|
||||
jvmargs = config.getMultiValue(appPrefix + "jvmarg");
|
||||
addAll(jvmargs, _jvmargs);
|
||||
addAll(config.getMultiValue(appPrefix + "jvmarg"), _jvmargs);
|
||||
}
|
||||
|
||||
// get the set of optimum JVM arguments
|
||||
_optimumJvmArgs = config.getMultiValue("optimum_jvmarg");
|
||||
|
||||
// transfer our application arguments
|
||||
String[] appargs = config.getMultiValue(appPrefix + "apparg");
|
||||
addAll(appargs, _appargs);
|
||||
addAll(config.getMultiValue(appPrefix + "apparg"), _appargs);
|
||||
|
||||
// add the launch specific application arguments
|
||||
_appargs.addAll(_envc.appArgs);
|
||||
@@ -742,27 +784,13 @@ public class Application
|
||||
// look for custom arguments
|
||||
fillAssignmentListFromPairs("extra.txt", _txtJvmArgs);
|
||||
|
||||
// determine whether we want to allow offline operation (defaults to false)
|
||||
_allowOffline = config.getBoolean("allow_offline");
|
||||
|
||||
// look for a debug.txt file which causes us to run in java.exe on Windows so that we can
|
||||
// obtain a thread dump of the running JVM
|
||||
_windebug = getLocalPath("debug.txt").exists();
|
||||
|
||||
// whether to cache code resources and launch from cache
|
||||
_useCodeCache = config.getBoolean("use_code_cache");
|
||||
_codeCacheRetentionDays = config.getInt("code_cache_retention_days", 7);
|
||||
|
||||
// maximum simultaneous downloads
|
||||
_maxConcDownloads = Math.max(1, config.getInt("max_concurrent_downloads",
|
||||
SysProps.threadPoolSize()));
|
||||
|
||||
// extract some info used to configure our child process on macOS
|
||||
_dockName = config.getString("ui.name");
|
||||
_dockIconPath = config.getString("ui.mac_dock_icon", "../desktop.icns");
|
||||
|
||||
_verifyTimeout = config.getInt("verify_timeout", 60);
|
||||
return config;
|
||||
// look for a debug.txt file which causes us to run in java.exe on Windows so that we can
|
||||
// obtain a thread dump of the running JVM
|
||||
_windebug = getLocalPath("debug.txt").exists();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -791,8 +819,7 @@ public class Application
|
||||
* Returns a URL from which the specified path can be fetched. Our application base URL is
|
||||
* properly versioned and combined with the supplied path.
|
||||
*/
|
||||
public URL getRemoteURL (String path)
|
||||
throws MalformedURLException
|
||||
public URL getRemoteURL (String path) throws MalformedURLException
|
||||
{
|
||||
return new URL(_vappbase, encodePath(path));
|
||||
}
|
||||
@@ -882,7 +909,7 @@ public class Application
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to redownload the <code>getdown.txt</code> file based on information parsed from a
|
||||
* Attempts to redownload the {@code getdown.txt} file based on information parsed from a
|
||||
* previous call to {@link #init}.
|
||||
*/
|
||||
public void attemptRecovery (StatusDisplay status)
|
||||
@@ -893,7 +920,7 @@ public class Application
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads and replaces the <code>getdown.txt</code> and <code>digest.txt</code> files with
|
||||
* Downloads and replaces the {@code getdown.txt} and {@code digest.txt} files with
|
||||
* those for the target version of our application.
|
||||
*/
|
||||
public void updateMetadata ()
|
||||
@@ -904,7 +931,7 @@ public class Application
|
||||
_vappbase = createVAppBase(_targetVersion);
|
||||
} catch (MalformedURLException mue) {
|
||||
String err = MessageUtil.tcompose("m.invalid_appbase", _appbase);
|
||||
throw (IOException) new IOException(err).initCause(mue);
|
||||
throw new IOException(err, mue);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -961,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");
|
||||
@@ -1080,7 +1099,7 @@ public class Application
|
||||
for (String jvmarg : _jvmargs) {
|
||||
if (jvmarg.startsWith("-D")) {
|
||||
jvmarg = processArg(jvmarg.substring(2));
|
||||
int eqidx = jvmarg.indexOf("=");
|
||||
int eqidx = jvmarg.indexOf('=');
|
||||
if (eqidx == -1) {
|
||||
log.warning("Bogus system property: '" + jvmarg + "'?");
|
||||
} else {
|
||||
@@ -1121,10 +1140,6 @@ public class Application
|
||||
/** Replaces the application directory and version in any argument. */
|
||||
protected String processArg (String arg)
|
||||
{
|
||||
if (arg == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
arg = arg.replace("%APPDIR%", getAppDir().getAbsolutePath());
|
||||
arg = arg.replace("%VERSION%", String.valueOf(_version));
|
||||
|
||||
@@ -1146,8 +1161,8 @@ public class Application
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the <code>digest.txt</code> file and verifies the contents of both that file and the
|
||||
* <code>getdown.text</code> file. Then it loads the <code>version.txt</code> and decides
|
||||
* Loads the {@code digest.txt} file and verifies the contents of both that file and the
|
||||
* {@code getdown.text} file. Then it loads the {@code version.txt} and decides
|
||||
* whether or not the application needs to be updated or whether we can proceed to verification
|
||||
* and execution.
|
||||
*
|
||||
@@ -1234,11 +1249,11 @@ 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))) {
|
||||
if (pair[0].equals("version")) {
|
||||
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) {
|
||||
// replace the file with the newest version
|
||||
@@ -1444,7 +1459,7 @@ public class Application
|
||||
protected URL createVAppBase (long version)
|
||||
throws MalformedURLException
|
||||
{
|
||||
String url = version < 0 ? _appbase : _appbase.replace("%VERSION%", "" + version);
|
||||
String url = version < 0 ? _appbase : _appbase.replace("%VERSION%", String.valueOf(version));
|
||||
return HostWhitelist.verify(new URL(url));
|
||||
}
|
||||
|
||||
@@ -1461,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);
|
||||
}
|
||||
@@ -1604,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");
|
||||
|
||||
@@ -1615,30 +1628,11 @@ public class Application
|
||||
} catch (Exception e) {
|
||||
log.warning("Requested to download invalid control file",
|
||||
"appbase", _vappbase, "path", path, "error", e);
|
||||
throw (IOException) new IOException("Invalid path '" + path + "'.").initCause(e);
|
||||
throw new IOException("Invalid path '" + path + "'.", e);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1652,9 +1646,7 @@ public class Application
|
||||
/** Helper function to add all values in {@code values} (if non-null) to {@code target}. */
|
||||
protected static void addAll (String[] values, List<String> target) {
|
||||
if (values != null) {
|
||||
for (String value : values) {
|
||||
target.add(value);
|
||||
}
|
||||
Collections.addAll(target, values);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1737,7 +1729,6 @@ public class Application
|
||||
}
|
||||
|
||||
protected final EnvConfig _envc;
|
||||
protected File _config;
|
||||
protected Digest _digest;
|
||||
|
||||
protected long _version = -1;
|
||||
|
||||
@@ -21,7 +21,7 @@ import static com.threerings.getdown.Log.log;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* Manages the <code>digest.txt</code> file and the computing and processing of digests for an
|
||||
* Manages the {@code digest.txt} file and the computing and processing of digests for an
|
||||
* application.
|
||||
*/
|
||||
public class Digest
|
||||
@@ -72,8 +72,7 @@ public class Digest
|
||||
digests.put(rsrc, rsrc.computeDigest(fversion, md, null));
|
||||
completed.add(rsrc);
|
||||
} catch (Throwable t) {
|
||||
completed.add(new IOException("Error computing digest for: " + rsrc).
|
||||
initCause(t));
|
||||
completed.add(new IOException("Error computing digest for: " + rsrc, t));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -88,7 +87,7 @@ public class Digest
|
||||
if (done instanceof IOException) {
|
||||
throw (IOException)done;
|
||||
} else if (done instanceof Resource) {
|
||||
pending.remove((Resource)done);
|
||||
pending.remove(done);
|
||||
} else {
|
||||
throw new AssertionError("What is this? " + done);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ public final class EnvConfig {
|
||||
|
||||
/** Used to report problems or feedback by {@link #create}. */
|
||||
public static final class Note {
|
||||
public static enum Level { INFO, WARN, ERROR };
|
||||
public enum Level { INFO, WARN, ERROR }
|
||||
public static Note info (String msg) { return new Note(Level.INFO, msg); }
|
||||
public static Note warn (String msg) { return new Note(Level.WARN, msg); }
|
||||
public static Note error (String msg) { return new Note(Level.ERROR, msg); }
|
||||
|
||||
@@ -23,7 +23,7 @@ import static com.threerings.getdown.Log.log;
|
||||
public class Resource implements Comparable<Resource>
|
||||
{
|
||||
/** Defines special attributes for resources. */
|
||||
public static enum Attr {
|
||||
public enum Attr {
|
||||
/** Indicates that the resource should be unpacked. */
|
||||
UNPACK,
|
||||
/** If present, when unpacking a resource, any directories created by the newly
|
||||
@@ -35,7 +35,7 @@ public class Resource implements Comparable<Resource>
|
||||
PRELOAD,
|
||||
/** Indicates that the resource is a jar containing native libs. */
|
||||
NATIVE
|
||||
};
|
||||
}
|
||||
|
||||
public static final EnumSet<Attr> NORMAL = EnumSet.noneOf(Attr.class);
|
||||
public static final EnumSet<Attr> UNPACK = EnumSet.of(Attr.UNPACK);
|
||||
|
||||
@@ -16,7 +16,7 @@ import com.threerings.getdown.util.VersionUtil;
|
||||
* accessor so that it's easy to see all of the secret system property arguments that Getdown makes
|
||||
* use of.
|
||||
*/
|
||||
public class SysProps
|
||||
public final class SysProps
|
||||
{
|
||||
/** Configures the appdir (in lieu of passing it in argv). Usage: {@code -Dappdir=foo}. */
|
||||
public static String appDir () {
|
||||
@@ -101,6 +101,16 @@ public 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;
|
||||
}
|
||||
@@ -11,7 +11,7 @@ package com.threerings.getdown.spi;
|
||||
public interface ProxyAuth
|
||||
{
|
||||
/** Credentials for a proxy server. */
|
||||
public static class Credentials {
|
||||
class Credentials {
|
||||
public final String username;
|
||||
public final String password;
|
||||
public Credentials (String username, String password) {
|
||||
@@ -23,10 +23,10 @@ public interface ProxyAuth
|
||||
/**
|
||||
* Loads the credentials for the app installed in {@code appDir}.
|
||||
*/
|
||||
public Credentials loadCredentials (String appDir);
|
||||
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);
|
||||
void saveCredentials (String appDir, String username, String password);
|
||||
}
|
||||
|
||||
@@ -5,10 +5,17 @@
|
||||
|
||||
package com.threerings.getdown.tools;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
@@ -31,8 +38,8 @@ public class Differ
|
||||
/**
|
||||
* Creates a single patch file that contains the differences between
|
||||
* the two specified application directories. The patch file will be
|
||||
* created in the <code>nvdir</code> directory with name
|
||||
* <code>patchV.dat</code> where V is the old application version.
|
||||
* created in the {@code nvdir} directory with name
|
||||
* {@code patchV.dat} where V is the old application version.
|
||||
*/
|
||||
public void createDiff (File nvdir, File ovdir, boolean verbose)
|
||||
throws IOException
|
||||
@@ -53,13 +60,13 @@ public class Differ
|
||||
|
||||
Application oapp = new Application(new EnvConfig(ovdir));
|
||||
oapp.init(false);
|
||||
ArrayList<Resource> orsrcs = new ArrayList<>();
|
||||
List<Resource> orsrcs = new ArrayList<>();
|
||||
orsrcs.addAll(oapp.getCodeResources());
|
||||
orsrcs.addAll(oapp.getResources());
|
||||
|
||||
Application napp = new Application(new EnvConfig(nvdir));
|
||||
napp.init(false);
|
||||
ArrayList<Resource> nrsrcs = new ArrayList<>();
|
||||
List<Resource> nrsrcs = new ArrayList<>();
|
||||
nrsrcs.addAll(napp.getCodeResources());
|
||||
nrsrcs.addAll(napp.getResources());
|
||||
|
||||
@@ -83,8 +90,8 @@ public class Differ
|
||||
}
|
||||
}
|
||||
|
||||
protected void createPatch (File patch, ArrayList<Resource> orsrcs,
|
||||
ArrayList<Resource> nrsrcs, boolean verbose)
|
||||
protected void createPatch (File patch, List<Resource> orsrcs,
|
||||
List<Resource> nrsrcs, boolean verbose)
|
||||
throws IOException
|
||||
{
|
||||
int version = Digest.VERSION;
|
||||
@@ -200,7 +207,7 @@ public class Differ
|
||||
Differ differ = new Differ();
|
||||
boolean verbose = false;
|
||||
int aidx = 0;
|
||||
if (args[0].equals("-verbose")) {
|
||||
if ("-verbose".equals(args[0])) {
|
||||
verbose = true;
|
||||
aidx++;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.threerings.getdown.data.Digest;
|
||||
import com.threerings.getdown.data.EnvConfig;
|
||||
import com.threerings.getdown.data.Resource;
|
||||
import com.threerings.getdown.util.Base64;
|
||||
import com.threerings.getdown.util.Config;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
@@ -74,8 +75,11 @@ public class Digester
|
||||
System.out.println("Generating digest file '" + target + "'...");
|
||||
|
||||
// create our application and instruct it to parse its business
|
||||
Application app = new Application(new EnvConfig(appdir));
|
||||
app.init(false);
|
||||
EnvConfig envc = new EnvConfig(appdir);
|
||||
Application app = new Application(envc);
|
||||
Config config = Application.readConfig(envc, false);
|
||||
app.initBase(config);
|
||||
app.initResources(config);
|
||||
|
||||
List<Resource> rsrcs = new ArrayList<>();
|
||||
rsrcs.add(app.getConfigResource());
|
||||
@@ -86,6 +90,9 @@ public class Digester
|
||||
rsrcs.addAll(ag.rsrcs);
|
||||
}
|
||||
|
||||
// reinit app just to verify that getdown.txt has valid format
|
||||
app.init(true);
|
||||
|
||||
// now generate the digest file
|
||||
Digest.createDigest(version, rsrcs, target);
|
||||
}
|
||||
|
||||
@@ -67,15 +67,15 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
public class JarDiff implements JarDiffCodes
|
||||
{
|
||||
private static final int DEFAULT_READ_SIZE = 2048;
|
||||
private static byte[] newBytes = new byte[DEFAULT_READ_SIZE];
|
||||
private static byte[] oldBytes = new byte[DEFAULT_READ_SIZE];
|
||||
private static final byte[] newBytes = new byte[DEFAULT_READ_SIZE];
|
||||
private static final byte[] oldBytes = new byte[DEFAULT_READ_SIZE];
|
||||
|
||||
// The JARDiff.java is the stand-alone jardiff.jar tool. Thus, we do not depend on Globals.java
|
||||
// and other stuff here. Instead, we use an explicit _debug flag.
|
||||
private static boolean _debug;
|
||||
|
||||
/**
|
||||
* Creates a patch from the two passed in files, writing the result to <code>os</code>.
|
||||
* Creates a patch from the two passed in files, writing the result to {@code os}.
|
||||
*/
|
||||
public static void createPatch (String oldPath, String newPath,
|
||||
OutputStream os, boolean minimal) throws IOException
|
||||
@@ -83,10 +83,10 @@ public class JarDiff implements JarDiffCodes
|
||||
try (ZipFile2 oldArchive = new ZipFile2(oldPath);
|
||||
ZipFile2 newArchive = new ZipFile2(newPath)) {
|
||||
|
||||
HashMap<String,String> moved = new HashMap<>();
|
||||
HashSet<String> implicit = new HashSet<>();
|
||||
HashSet<String> moveSrc = new HashSet<>();
|
||||
HashSet<String> newEntries = new HashSet<>();
|
||||
Map<String,String> moved = new HashMap<>();
|
||||
Set<String> implicit = new HashSet<>();
|
||||
Set<String> moveSrc = new HashSet<>();
|
||||
Set<String> newEntries = new HashSet<>();
|
||||
|
||||
// FIRST PASS
|
||||
// Go through the entries in new archive and determine which files are candidates for
|
||||
@@ -157,7 +157,7 @@ public class JarDiff implements JarDiffCodes
|
||||
|
||||
// SECOND PASS: <deleted files> = <oldjarnames> - <implicitmoves> -
|
||||
// <source of move commands> - <new or modified entries>
|
||||
ArrayList<String> deleted = new ArrayList<>();
|
||||
List<String> deleted = new ArrayList<>();
|
||||
for (ZipEntry oldEntry : oldArchive) {
|
||||
String oldName = oldEntry.getName();
|
||||
if (!implicit.contains(oldName) && !moveSrc.contains(oldName)
|
||||
@@ -203,9 +203,9 @@ public class JarDiff implements JarDiffCodes
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the index file out to <code>jos</code>.
|
||||
* <code>oldEntries</code> gives the names of the files that were removed,
|
||||
* <code>movedMap</code> maps from the new name to the old name.
|
||||
* Writes the index file out to {@code jos}.
|
||||
* {@code oldEntries} gives the names of the files that were removed,
|
||||
* {@code movedMap} maps from the new name to the old name.
|
||||
*/
|
||||
private static void createIndex (ZipOutputStream jos, List<String> oldEntries,
|
||||
Map<String,String> movedMap)
|
||||
@@ -286,7 +286,7 @@ public class JarDiff implements JarDiffCodes
|
||||
*/
|
||||
private static class ZipFile2 implements Iterable<ZipEntry>, Closeable
|
||||
{
|
||||
private ZipFile _archive;
|
||||
private final ZipFile _archive;
|
||||
private List<ZipEntry> _entries;
|
||||
private HashMap<String,ZipEntry> _nameToEntryMap;
|
||||
private HashMap<Long,LinkedList<ZipEntry>> _crcToEntryMap;
|
||||
@@ -384,7 +384,7 @@ public class JarDiff implements JarDiffCodes
|
||||
|
||||
public String hasSameContent (ZipFile2 file, ZipEntry entry) throws IOException {
|
||||
String thisName = null;
|
||||
Long crcL = Long.valueOf(entry.getCrc());
|
||||
Long crcL = entry.getCrc();
|
||||
// check if this archive contains files with the passed in entry's crc
|
||||
if (_crcToEntryMap.containsKey(crcL)) {
|
||||
// get the Linked List with files with the crc
|
||||
@@ -419,7 +419,7 @@ public class JarDiff implements JarDiffCodes
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipEntry entry = entries.nextElement();
|
||||
long crc = entry.getCrc();
|
||||
Long crcL = Long.valueOf(crc);
|
||||
Long crcL = crc;
|
||||
if (_debug) {
|
||||
System.out.println("\t" + entry.getName() + " CRC " + crc);
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ public class JarDiffPatcher implements JarDiffCodes
|
||||
{
|
||||
int index = 0;
|
||||
int length = path.length();
|
||||
ArrayList<String> sub = new ArrayList<>();
|
||||
List<String> sub = new ArrayList<>();
|
||||
|
||||
while (index < length) {
|
||||
while (index < length && Character.isWhitespace
|
||||
@@ -223,9 +223,8 @@ public class JarDiffPatcher implements JarDiffCodes
|
||||
index++;
|
||||
}
|
||||
if (index < length) {
|
||||
int start = index;
|
||||
int last = start;
|
||||
String subString = null;
|
||||
int last = index;
|
||||
StringBuilder subString = null;
|
||||
|
||||
while (index < length) {
|
||||
char aChar = path.charAt(index);
|
||||
@@ -233,9 +232,9 @@ public class JarDiffPatcher implements JarDiffCodes
|
||||
path.charAt(index + 1) == ' ') {
|
||||
|
||||
if (subString == null) {
|
||||
subString = path.substring(last, index);
|
||||
subString = new StringBuilder(path.substring(last, index));
|
||||
} else {
|
||||
subString += path.substring(last, index);
|
||||
subString.append(path, last, index);
|
||||
}
|
||||
last = ++index;
|
||||
} else if (Character.isWhitespace(aChar)) {
|
||||
@@ -245,12 +244,14 @@ public class JarDiffPatcher implements JarDiffCodes
|
||||
}
|
||||
if (last != index) {
|
||||
if (subString == null) {
|
||||
subString = path.substring(last, index);
|
||||
subString = new StringBuilder(path.substring(last, index));
|
||||
} else {
|
||||
subString += path.substring(last, index);
|
||||
subString.append(path, last, index);
|
||||
}
|
||||
}
|
||||
sub.add(subString);
|
||||
if (subString != null) {
|
||||
sub.add(subString.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return sub;
|
||||
|
||||
@@ -24,7 +24,7 @@ import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
* href="http://www.ietf.org/rfc/rfc2045.txt">2045</a> and <a
|
||||
* href="http://www.ietf.org/rfc/rfc3548.txt">3548</a>.
|
||||
*/
|
||||
public class Base64 {
|
||||
public final class Base64 {
|
||||
/**
|
||||
* Default values for encoder/decoder flags.
|
||||
*/
|
||||
@@ -68,7 +68,7 @@ public class Base64 {
|
||||
// shared code
|
||||
// --------------------------------------------------------
|
||||
|
||||
/* package */ static abstract class Coder {
|
||||
/* package */ abstract static class Coder {
|
||||
public byte[] output;
|
||||
public int op;
|
||||
|
||||
@@ -178,7 +178,7 @@ public class Base64 {
|
||||
* Lookup table for turning bytes into their position in the
|
||||
* Base64 alphabet.
|
||||
*/
|
||||
private static final int DECODE[] = {
|
||||
private static final int[] DECODE = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
|
||||
@@ -201,7 +201,7 @@ public class Base64 {
|
||||
* Decode lookup table for the "web safe" variant (RFC 3548
|
||||
* sec. 4) where - and _ replace + and /.
|
||||
*/
|
||||
private static final int DECODE_WEBSAFE[] = {
|
||||
private static final int[] DECODE_WEBSAFE = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
|
||||
@@ -236,7 +236,7 @@ public class Base64 {
|
||||
private int state; // state number (0 to 6)
|
||||
private int value;
|
||||
|
||||
final private int[] alphabet;
|
||||
private final int[] alphabet;
|
||||
|
||||
public Decoder(int flags, byte[] output) {
|
||||
this.output = output;
|
||||
@@ -541,7 +541,7 @@ public class Base64 {
|
||||
* Lookup table for turning Base64 alphabet positions (6 bits)
|
||||
* into output bytes.
|
||||
*/
|
||||
private static final byte ENCODE[] = {
|
||||
private static final byte[] ENCODE = {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||
@@ -552,21 +552,21 @@ public class Base64 {
|
||||
* Lookup table for turning Base64 alphabet positions (6 bits)
|
||||
* into output bytes.
|
||||
*/
|
||||
private static final byte ENCODE_WEBSAFE[] = {
|
||||
private static final byte[] ENCODE_WEBSAFE = {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_',
|
||||
};
|
||||
|
||||
final private byte[] tail;
|
||||
private final byte[] tail;
|
||||
/* package */ int tailLen;
|
||||
private int count;
|
||||
|
||||
final public boolean do_padding;
|
||||
final public boolean do_newline;
|
||||
final public boolean do_cr;
|
||||
final private byte[] alphabet;
|
||||
public final boolean do_padding;
|
||||
public final boolean do_newline;
|
||||
public final boolean do_cr;
|
||||
private final byte[] alphabet;
|
||||
|
||||
public Encoder(int flags, byte[] output) {
|
||||
this.output = output;
|
||||
@@ -618,7 +618,7 @@ public class Base64 {
|
||||
((input[p++] & 0xff) << 8) |
|
||||
(input[p++] & 0xff);
|
||||
tailLen = 0;
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
|
||||
@@ -8,11 +8,11 @@ package com.threerings.getdown.util;
|
||||
/**
|
||||
* Utilities for handling ARGB colors.
|
||||
*/
|
||||
public class Color
|
||||
public final class Color
|
||||
{
|
||||
public final static int CLEAR = 0x00000000;
|
||||
public final static int WHITE = 0xFFFFFFFF;
|
||||
public final static int BLACK = 0xFF000000;
|
||||
public static final int CLEAR = 0x00000000;
|
||||
public static final int WHITE = 0xFFFFFFFF;
|
||||
public static final int BLACK = 0xFF000000;
|
||||
|
||||
public static float brightness (int argb) {
|
||||
// TODO: we're ignoring alpha here...
|
||||
|
||||
@@ -62,7 +62,7 @@ public class Config
|
||||
*
|
||||
* @param opts options that influence the parsing. See {@link #createOpts}.
|
||||
*
|
||||
* @return a list of <code>String[]</code> instances containing the key/value pairs in the
|
||||
* @return a list of {@code String[]} instances containing the key/value pairs in the
|
||||
* order they were parsed from the file.
|
||||
*/
|
||||
public static List<String[]> parsePairs (File source, ParseOpts opts)
|
||||
@@ -83,7 +83,7 @@ public class Config
|
||||
List<String[]> pairs = new ArrayList<>();
|
||||
for (String line : FileUtil.readLines(source)) {
|
||||
// nix comments
|
||||
int cidx = line.indexOf("#");
|
||||
int cidx = line.indexOf('#');
|
||||
if (opts.strictComments ? cidx == 0 : cidx != -1) {
|
||||
line = line.substring(0, cidx);
|
||||
}
|
||||
@@ -97,7 +97,7 @@ public class Config
|
||||
// parse our key/value pair
|
||||
String[] pair = new String[2];
|
||||
// if we're biasing toward key, put all the extra = in the key rather than the value
|
||||
int eidx = opts.biasToKey ? line.lastIndexOf("=") : line.indexOf("=");
|
||||
int eidx = opts.biasToKey ? line.lastIndexOf('=') : line.indexOf('=');
|
||||
if (eidx != -1) {
|
||||
pair[0] = line.substring(0, eidx).trim();
|
||||
pair[1] = line.substring(eidx+1).trim();
|
||||
@@ -108,7 +108,7 @@ public class Config
|
||||
|
||||
// if the pair has an os qualifier, we need to process it
|
||||
if (pair[1].startsWith("[")) {
|
||||
int qidx = pair[1].indexOf("]");
|
||||
int qidx = pair[1].indexOf(']');
|
||||
if (qidx == -1) {
|
||||
log.warning("Bogus platform specifier", "key", pair[0], "value", pair[1]);
|
||||
continue; // omit the pair entirely
|
||||
@@ -351,7 +351,7 @@ public class Config
|
||||
protected static boolean checkQualifiers (String quals, String osname, String osarch)
|
||||
{
|
||||
if (quals.startsWith("!")) {
|
||||
if (quals.indexOf(",") != -1) { // sanity check
|
||||
if (quals.contains(",")) { // sanity check
|
||||
log.warning("Multiple qualifiers cannot be used when one of the qualifiers " +
|
||||
"is negative", "quals", quals);
|
||||
return false;
|
||||
@@ -371,7 +371,7 @@ public class Config
|
||||
{
|
||||
String[] bits = qual.trim().toLowerCase(Locale.ROOT).split("-");
|
||||
String os = bits[0], arch = (bits.length > 1) ? bits[1] : "";
|
||||
return (osname.indexOf(os) != -1) && (osarch.indexOf(arch) != -1);
|
||||
return (osname.contains(os)) && (osarch.contains(arch));
|
||||
}
|
||||
|
||||
private final Map<String, Object> _data;
|
||||
|
||||
@@ -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 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);
|
||||
}
|
||||
}
|
||||
@@ -17,19 +17,19 @@ import static com.threerings.getdown.Log.log;
|
||||
* Useful routines for launching Java applications from within other Java
|
||||
* applications.
|
||||
*/
|
||||
public class LaunchUtil
|
||||
public final class LaunchUtil
|
||||
{
|
||||
/** The default directory into which a local VM installation should be unpacked. */
|
||||
public static final String LOCAL_JAVA_DIR = "java_vm";
|
||||
|
||||
/**
|
||||
* Writes a <code>version.txt</code> file into the specified application directory and
|
||||
* Writes a {@code version.txt} file into the specified application directory and
|
||||
* attempts to relaunch Getdown in that directory which will cause it to upgrade to the newly
|
||||
* specified version and relaunch the application.
|
||||
*
|
||||
* @param appdir the directory in which the application is installed.
|
||||
* @param getdownJarName the name of the getdown jar file in the application directory. This is
|
||||
* probably <code>getdown-pro.jar</code> or <code>getdown-retro-pro.jar</code> if you are using
|
||||
* probably {@code getdown-pro.jar} or {@code getdown-retro-pro.jar} if you are using
|
||||
* the results of the standard build.
|
||||
* @param newVersion the new version to which Getdown will update when it is executed.
|
||||
* @param javaLocalDir the name of the directory (inside {@code appdir}) that contains a
|
||||
@@ -41,7 +41,7 @@ public class LaunchUtil
|
||||
* after making this call as it will be upgraded and restarted. If false is returned, the
|
||||
* application should tell the user that they must restart the application manually.
|
||||
*
|
||||
* @exception IOException thrown if we were unable to create the <code>version.txt</code> file
|
||||
* @exception IOException thrown if we were unable to create the {@code version.txt} file
|
||||
* in the supplied application directory. If the version.txt file cannot be created, restarting
|
||||
* Getdown will not cause the application to be upgraded, so the application will have to
|
||||
* resort to telling the user that it is in a bad way.
|
||||
@@ -177,23 +177,23 @@ public class LaunchUtil
|
||||
public static boolean mustMonitorChildren ()
|
||||
{
|
||||
String osname = System.getProperty("os.name", "").toLowerCase(Locale.ROOT);
|
||||
return (osname.indexOf("windows 98") != -1 || osname.indexOf("windows me") != -1);
|
||||
return (osname.contains("windows 98") || osname.contains("windows me"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we're running in a JVM that identifies its operating system as Windows.
|
||||
*/
|
||||
public static final boolean isWindows () { return _isWindows; }
|
||||
public static boolean isWindows () { return _isWindows; }
|
||||
|
||||
/**
|
||||
* Returns true if we're running in a JVM that identifies its operating system as MacOS.
|
||||
*/
|
||||
public static final boolean isMacOS () { return _isMacOS; }
|
||||
public static boolean isMacOS () { return _isMacOS; }
|
||||
|
||||
/**
|
||||
* Returns true if we're running in a JVM that identifies its operating system as Linux.
|
||||
*/
|
||||
public static final boolean isLinux () { return _isLinux; }
|
||||
public static boolean isLinux () { return _isLinux; }
|
||||
|
||||
/**
|
||||
* Checks whether a Java Virtual Machine can be located in the supplied path.
|
||||
@@ -232,10 +232,9 @@ public class LaunchUtil
|
||||
try {
|
||||
String osname = System.getProperty("os.name");
|
||||
osname = (osname == null) ? "" : osname;
|
||||
_isWindows = (osname.indexOf("Windows") != -1);
|
||||
_isMacOS = (osname.indexOf("Mac OS") != -1 ||
|
||||
osname.indexOf("MacOS") != -1);
|
||||
_isLinux = (osname.indexOf("Linux") != -1);
|
||||
_isWindows = (osname.contains("Windows"));
|
||||
_isMacOS = (osname.contains("Mac OS") || osname.contains("MacOS"));
|
||||
_isLinux = (osname.contains("Linux"));
|
||||
} catch (Exception e) {
|
||||
// can't grab system properties; we'll just pretend we're not on any of these OSes
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
package com.threerings.getdown.util;
|
||||
|
||||
public class MessageUtil {
|
||||
public final class MessageUtil {
|
||||
|
||||
/**
|
||||
* Returns whether or not the provided string is tainted. See {@link #taint}. Null strings
|
||||
@@ -101,11 +101,11 @@ public class MessageUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to escape single quotes so that they are not interpreted by <code>MessageFormat</code>.
|
||||
* Used to escape single quotes so that they are not interpreted by {@code MessageFormat}.
|
||||
* As we assume all single quotes are to be escaped, we cannot use the characters
|
||||
* <code>{</code> and <code>}</code> in our translation strings, but this is a small price to
|
||||
* pay to have to differentiate between messages that will and won't eventually be parsed by a
|
||||
* <code>MessageFormat</code> instance.
|
||||
* {@code MessageFormat} instance.
|
||||
*/
|
||||
public static String escape (String message)
|
||||
{
|
||||
|
||||
@@ -14,5 +14,5 @@ public interface ProgressObserver
|
||||
* Informs the observer that we have completed the specified
|
||||
* percentage of the process.
|
||||
*/
|
||||
public void progress (int percent);
|
||||
void progress (int percent);
|
||||
}
|
||||
|
||||
@@ -11,11 +11,10 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
public class StreamUtil {
|
||||
public final class StreamUtil {
|
||||
/**
|
||||
* Convenient close for a stream. Use in a finally clause and love life.
|
||||
*/
|
||||
|
||||
@@ -7,14 +7,14 @@ package com.threerings.getdown.util;
|
||||
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
public class StringUtil {
|
||||
public final class StringUtil {
|
||||
|
||||
/**
|
||||
* @return true if the specified string could be a valid URL (contains no illegal characters)
|
||||
*/
|
||||
public static boolean couldBeValidUrl (String url)
|
||||
{
|
||||
return url.matches("[A-Za-z0-9\\-\\._~:/\\?#\\[\\]@!$&'\\(\\)\\*\\+,;=%]+");
|
||||
return url.matches("[A-Za-z0-9\\-._~:/?#\\[\\]@!$&'()*+,;=%]+");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +84,7 @@ public class StringUtil {
|
||||
source = source.replace(",,", "%COMMA%");
|
||||
|
||||
// count up the number of tokens
|
||||
while ((tpos = source.indexOf(",", tpos+1)) != -1) {
|
||||
while ((tpos = source.indexOf(',', tpos+1)) != -1) {
|
||||
tcount++;
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ public class StringUtil {
|
||||
tpos = -1; tcount = 0;
|
||||
|
||||
// do the split
|
||||
while ((tpos = source.indexOf(",", tpos+1)) != -1) {
|
||||
while ((tpos = source.indexOf(',', tpos+1)) != -1) {
|
||||
tokens[tcount] = source.substring(tstart, tpos);
|
||||
tokens[tcount] = tokens[tcount].trim().replace("%COMMA%", ",");
|
||||
if (intern) {
|
||||
@@ -119,7 +119,7 @@ public class StringUtil {
|
||||
|
||||
/**
|
||||
* Generates a string from the supplied bytes that is the HEX encoded representation of those
|
||||
* bytes. Returns the empty string for a <code>null</code> or empty byte array.
|
||||
* bytes. Returns the empty string for a {@code null} or empty byte array.
|
||||
*
|
||||
* @param bytes the bytes for which we want a string representation.
|
||||
* @param count the number of bytes to stop at (which will be coerced into being {@code <=} the
|
||||
@@ -185,7 +185,7 @@ public class StringUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for the various <code>join</code> methods.
|
||||
* Helper function for the various {@code join} methods.
|
||||
*/
|
||||
protected static String join (Object[] values, String separator, boolean escape)
|
||||
{
|
||||
@@ -201,6 +201,6 @@ public class StringUtil {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/** Used by {@link #hexlate} and {@link #unhexlate}. */
|
||||
/** Used by {@link #hexlate}. */
|
||||
protected static final String XLATE = "0123456789abcdef";
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
/**
|
||||
* Version related utilities.
|
||||
*/
|
||||
public class VersionUtil
|
||||
public final class VersionUtil
|
||||
{
|
||||
/**
|
||||
* Reads a version number from a file.
|
||||
|
||||
@@ -38,6 +38,12 @@
|
||||
<version>1.0</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -189,6 +195,7 @@
|
||||
<lib>${java.home}/jmods/java.base.jmod</lib>
|
||||
<lib>${java.home}/jmods/java.desktop.jmod</lib>
|
||||
<lib>${java.home}/jmods/java.logging.jmod</lib>
|
||||
<lib>${java.home}/jmods/java.scripting.jmod</lib>
|
||||
<lib>${java.home}/jmods/jdk.jsobject.jmod</lib>
|
||||
</libs>
|
||||
</configuration>
|
||||
|
||||
@@ -72,7 +72,7 @@ public final class AbortPanel extends JFrame
|
||||
public void actionPerformed (ActionEvent e)
|
||||
{
|
||||
String cmd = e.getActionCommand();
|
||||
if (cmd.equals("ok")) {
|
||||
if ("ok".equals(cmd)) {
|
||||
System.exit(0);
|
||||
} else {
|
||||
setVisible(false);
|
||||
|
||||
@@ -22,7 +22,15 @@ import java.io.InputStreamReader;
|
||||
import java.io.PrintStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.AbstractAction;
|
||||
@@ -31,24 +39,44 @@ import javax.swing.JFrame;
|
||||
import javax.swing.JLayeredPane;
|
||||
|
||||
import com.samskivert.swing.util.SwingUtil;
|
||||
import com.threerings.getdown.data.*;
|
||||
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.*;
|
||||
|
||||
import com.threerings.getdown.util.Config;
|
||||
import com.threerings.getdown.util.FileUtil;
|
||||
import com.threerings.getdown.util.LaunchUtil;
|
||||
import com.threerings.getdown.util.MessageUtil;
|
||||
import com.threerings.getdown.util.ProgressAggregator;
|
||||
import com.threerings.getdown.util.ProgressObserver;
|
||||
import com.threerings.getdown.util.StringUtil;
|
||||
import com.threerings.getdown.util.VersionUtil;
|
||||
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.
|
||||
@@ -75,7 +103,7 @@ public abstract class Getdown extends Thread
|
||||
// welcome to hell, where java can't cope with a classpath that contains jars that live
|
||||
// in a directory that contains a !, at least the same bug happens on all platforms
|
||||
String dir = envc.appDir.toString();
|
||||
if (dir.equals(".")) {
|
||||
if (".".equals(dir)) {
|
||||
dir = System.getProperty("user.dir");
|
||||
}
|
||||
String errmsg = "The directory in which this application is installed:\n" + dir +
|
||||
@@ -115,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
|
||||
@@ -130,79 +178,27 @@ public abstract class Getdown extends Thread
|
||||
File instdir = _app.getLocalPath("");
|
||||
if (!instdir.canWrite()) {
|
||||
String path = instdir.getPath();
|
||||
if (path.equals(".")) {
|
||||
if (".".equals(path)) {
|
||||
path = System.getProperty("user.dir");
|
||||
}
|
||||
fail(MessageUtil.tcompose("m.readonly_error", path));
|
||||
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);
|
||||
String msg = e.getMessage();
|
||||
if (msg == null) {
|
||||
msg = MessageUtil.compose("m.unknown_error", _ifc.installError);
|
||||
} else if (!msg.startsWith("m.")) {
|
||||
// try to do something sensible based on the type of error
|
||||
if (e instanceof FileNotFoundException) {
|
||||
msg = MessageUtil.compose(
|
||||
"m.missing_resource", MessageUtil.taint(msg), _ifc.installError);
|
||||
} else {
|
||||
msg = MessageUtil.compose(
|
||||
"m.init_error", MessageUtil.taint(msg), _ifc.installError);
|
||||
}
|
||||
}
|
||||
fail(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
@@ -211,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;
|
||||
}
|
||||
|
||||
@@ -228,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.
|
||||
@@ -258,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) {
|
||||
@@ -273,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 " +
|
||||
@@ -285,7 +304,7 @@ public abstract class Getdown extends Thread
|
||||
// Store the config modtime before waiting the delay amount of time
|
||||
long lastConfigModtime = config.lastModified();
|
||||
log.info("Waiting " + _delay + " minutes before beginning actual work.");
|
||||
Thread.sleep(_delay * 60 * 1000);
|
||||
TimeUnit.MINUTES.sleep(_delay);
|
||||
if (lastConfigModtime < config.lastModified()) {
|
||||
log.warning("getdown.txt was modified while getdown was waiting.");
|
||||
throw new MultipleGetdownRunning();
|
||||
@@ -334,11 +353,7 @@ public abstract class Getdown extends Thread
|
||||
|
||||
if (toDownload.size() > 0) {
|
||||
// we have resources to download, also note them as to-be-installed
|
||||
for (Resource r : toDownload) {
|
||||
if (!_toInstallResources.contains(r)) {
|
||||
_toInstallResources.add(r);
|
||||
}
|
||||
}
|
||||
_toInstallResources.addAll(toDownload);
|
||||
|
||||
try {
|
||||
// if any of our resources have already been marked valid this is not a
|
||||
@@ -422,24 +437,20 @@ public abstract class Getdown extends Thread
|
||||
throw new IOException("m.unable_to_repair");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warning("getdown() failed.", e);
|
||||
String msg = e.getMessage();
|
||||
if (msg == null) {
|
||||
msg = MessageUtil.compose("m.unknown_error", _ifc.installError);
|
||||
} else if (!msg.startsWith("m.")) {
|
||||
// try to do something sensible based on the type of error
|
||||
if (e instanceof FileNotFoundException) {
|
||||
msg = MessageUtil.compose(
|
||||
"m.missing_resource", MessageUtil.taint(msg), _ifc.installError);
|
||||
} else {
|
||||
msg = MessageUtil.compose(
|
||||
"m.init_error", MessageUtil.taint(msg), _ifc.installError);
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
// Since we're dead, clear off the 'time remaining' label along with displaying the
|
||||
// error message
|
||||
fail(msg);
|
||||
_app.releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,7 +536,7 @@ public abstract class Getdown extends Thread
|
||||
|
||||
// lastly regenerate the .jsa dump file that helps Java to start up faster
|
||||
String vmpath = LaunchUtil.getJVMBinaryPath(javaLocalDir, false);
|
||||
String[] command = new String[] { vmpath, "-Xshare:dump" };
|
||||
String[] command = { vmpath, "-Xshare:dump" };
|
||||
try {
|
||||
log.info("Regenerating classes.jsa for " + vmpath + "...");
|
||||
Runtime.getRuntime().exec(command);
|
||||
@@ -620,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");
|
||||
}
|
||||
@@ -732,7 +743,7 @@ public abstract class Getdown extends Thread
|
||||
long minshow = _ifc.minShowSeconds * 1000L;
|
||||
if (_container != null && uptime < minshow) {
|
||||
try {
|
||||
Thread.sleep(minshow - uptime);
|
||||
TimeUnit.MILLISECONDS.sleep(minshow - uptime);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
@@ -848,8 +859,23 @@ public abstract class Getdown extends Thread
|
||||
}
|
||||
}
|
||||
|
||||
private void fail (Exception e) {
|
||||
String msg = e.getMessage();
|
||||
if (msg == null) {
|
||||
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 = 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
|
||||
fail(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the status to indicate getdown has failed for the reason in <code>message</code>.
|
||||
* Update the status to indicate getdown has failed for the reason in {@code message}.
|
||||
*/
|
||||
protected void fail (String message)
|
||||
{
|
||||
@@ -930,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1000,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
|
||||
@@ -146,7 +146,7 @@ public class GetdownApp
|
||||
}
|
||||
|
||||
if (_ifc.iconImages != null) {
|
||||
ArrayList<Image> icons = new ArrayList<>();
|
||||
List<Image> icons = new ArrayList<>();
|
||||
for (String path : _ifc.iconImages) {
|
||||
Image img = loadImage(path);
|
||||
if (img == null) {
|
||||
@@ -190,7 +190,7 @@ public class GetdownApp
|
||||
String[] cmdarray;
|
||||
if (LaunchUtil.isWindows()) {
|
||||
String osName = System.getProperty("os.name", "");
|
||||
if (osName.indexOf("9") != -1 || osName.indexOf("Me") != -1) {
|
||||
if (osName.contains("9") || osName.contains("Me")) {
|
||||
cmdarray = new String[] {
|
||||
"command.com", "/c", "start", "\"" + url + "\"" };
|
||||
} else {
|
||||
@@ -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
|
||||
@@ -131,7 +140,7 @@ public final class ProxyPanel extends JPanel implements ActionListener
|
||||
public void actionPerformed (ActionEvent e)
|
||||
{
|
||||
String cmd = e.getActionCommand();
|
||||
if (cmd.equals("ok")) {
|
||||
if ("ok".equals(cmd)) {
|
||||
String user = null, pass = null;
|
||||
if (_useAuth.isSelected()) {
|
||||
user = _username.getText();
|
||||
@@ -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;
|
||||
|
||||
@@ -8,31 +8,41 @@ package com.threerings.getdown.launcher;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintStream;
|
||||
import java.io.Reader;
|
||||
import java.net.Authenticator;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Iterator;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
import javax.script.Bindings;
|
||||
import javax.script.Invocable;
|
||||
import javax.script.ScriptContext;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
|
||||
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.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;
|
||||
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
public class ProxyUtil {
|
||||
public final class ProxyUtil {
|
||||
|
||||
public static boolean autoDetectProxy (Application app)
|
||||
{
|
||||
@@ -57,25 +67,39 @@ public class ProxyUtil {
|
||||
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 ("ProxyEnable".equals(value.getName())) {
|
||||
enabled = "1".equals(value.getStringValue());
|
||||
}
|
||||
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);
|
||||
String[] hostPort = splitHostPort(value.getStringValue());
|
||||
rhost = hostPort[0];
|
||||
rport = hostPort[1];
|
||||
}
|
||||
if (value.getName().equals("AutoConfigURL")) {
|
||||
String acurl = value.getStringValue();
|
||||
Reader acjs = new InputStreamReader(new URL(acurl).openStream());
|
||||
// technically we should be returning all this info and trying each proxy
|
||||
// in succession, but that's complexity we'll leave for another day
|
||||
for (String proxy : findPACProxiesForURL(acjs, app.getRemoteURL(""))) {
|
||||
if (proxy.startsWith("PROXY ")) {
|
||||
String[] hostPort = splitHostPort(proxy.substring(6));
|
||||
rhost = hostPort[0];
|
||||
rport = hostPort[1];
|
||||
// TODO: is this valid? Does AutoConfigURL imply proxy enabled?
|
||||
enabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -97,32 +121,31 @@ public 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.");
|
||||
@@ -190,9 +213,14 @@ public 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;
|
||||
@@ -205,6 +233,54 @@ public class ProxyUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static class Resolver {
|
||||
public String dnsResolve (String host) {
|
||||
try {
|
||||
return InetAddress.getByName(host).toString();
|
||||
} catch (UnknownHostException uhe) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String[] findPACProxiesForURL (Reader pac, URL url) {
|
||||
ScriptEngineManager manager = new ScriptEngineManager();
|
||||
ScriptEngine engine = manager.getEngineByName("javascript");
|
||||
Bindings globals = engine.createBindings();
|
||||
globals.put("resolver", new Resolver());
|
||||
engine.setBindings(globals, ScriptContext.GLOBAL_SCOPE);
|
||||
try {
|
||||
URL utils = ProxyUtil.class.getResource("PacUtils.js");
|
||||
if (utils == null) {
|
||||
log.error("Unable to load PacUtils.js");
|
||||
return new String[0];
|
||||
}
|
||||
engine.eval(new InputStreamReader(utils.openStream()));
|
||||
Object res = engine.eval(pac);
|
||||
if (engine instanceof Invocable) {
|
||||
Object[] args = new Object[] { url.toString(), url.getHost() };
|
||||
res = ((Invocable) engine).invokeFunction("FindProxyForURL", args);
|
||||
}
|
||||
String[] proxies = res.toString().split(";");
|
||||
for (int ii = 0; ii < proxies.length; ii += 1) {
|
||||
proxies[ii] = proxies[ii].trim();
|
||||
}
|
||||
return proxies;
|
||||
} catch (Exception e) {
|
||||
log.warning("Failed to resolve PAC proxy", e);
|
||||
}
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
private static String[] splitHostPort (String hostPort) {
|
||||
int cidx = hostPort.indexOf(":");
|
||||
if (cidx == -1) {
|
||||
return new String[] { hostPort, null};
|
||||
} else {
|
||||
return new String[] { hostPort.substring(0, cidx), hostPort.substring(cidx+1) };
|
||||
}
|
||||
}
|
||||
|
||||
protected static final String PROXY_REGISTRY =
|
||||
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ public final class RotatingBackgrounds
|
||||
{
|
||||
public interface ImageLoader {
|
||||
/** Loads and returns the image with the supplied path. */
|
||||
public Image loadImage (String path);
|
||||
Image loadImage (String path);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,7 +35,7 @@ public final class RotatingBackgrounds
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sequence of images to be rotated through from <code>backgrounds</code>.
|
||||
* Create a sequence of images to be rotated through from {@code backgrounds}.
|
||||
*
|
||||
* Each String in backgrounds should be the path to the image, a semicolon, and the minimum
|
||||
* amount of time to display the image in seconds. Each image will be active for an equal
|
||||
|
||||
@@ -26,12 +26,9 @@ import com.samskivert.swing.Label;
|
||||
import com.samskivert.swing.LabelStyleConstants;
|
||||
import com.samskivert.swing.util.SwingUtil;
|
||||
import com.samskivert.util.Throttle;
|
||||
|
||||
import com.threerings.getdown.data.Application.UpdateInterface;
|
||||
import com.threerings.getdown.util.MessageUtil;
|
||||
import com.threerings.getdown.util.Rectangle;
|
||||
import com.threerings.getdown.util.StringUtil;
|
||||
|
||||
import static com.threerings.getdown.Log.log;
|
||||
|
||||
/**
|
||||
@@ -344,7 +341,7 @@ public final class StatusPanel extends JComponent
|
||||
{
|
||||
String msg = get(key);
|
||||
if (msg != null) return MessageFormat.format(MessageUtil.escape(msg), (Object[])args);
|
||||
return key + String.valueOf(Arrays.asList(args));
|
||||
return key + Arrays.asList(args);
|
||||
}
|
||||
|
||||
/** Used by {@link #setStatus}, and {@link #setProgress}. */
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Extracted from Mozilla Firefox 3.0.4, \mozilla\netwerk\base\src\nsProxyAutoConfig.js
|
||||
*/
|
||||
function dnsDomainIs (host, domain) {
|
||||
return (host.length >= domain.length &&
|
||||
host.substring(host.length - domain.length) == domain);
|
||||
}
|
||||
|
||||
function dnsDomainLevels (host) {
|
||||
return host.split('.').length-1;
|
||||
}
|
||||
|
||||
function convert_addr (ipchars) {
|
||||
var bytes = ipchars.split('.');
|
||||
var result = (((bytes[0] & 0xff) << 24) |
|
||||
((bytes[1] & 0xff) << 16) |
|
||||
((bytes[2] & 0xff) << 8) |
|
||||
( bytes[3] & 0xff ));
|
||||
return result;
|
||||
}
|
||||
|
||||
function dnsResolve (host) {
|
||||
return resolver.dnsResolve(host)
|
||||
}
|
||||
|
||||
function isInNet (addrOrHost, pattern, maskstr) {
|
||||
var testRE = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
||||
var test = testRE.exec(addrOrHost);
|
||||
if (test == null) {
|
||||
addrOrHost = dnsResolve(addrOrHost);
|
||||
if (addrOrHost == null) {
|
||||
return false;
|
||||
}
|
||||
} else if (test[1] > 255 || test[2] > 255 || test[3] > 255 || test[4] > 255) {
|
||||
return false; // not an IP address
|
||||
}
|
||||
var host = convert_addr(addrOrHost);
|
||||
var pat = convert_addr(pattern);
|
||||
var mask = convert_addr(maskstr);
|
||||
return ((host & mask) == (pat & mask));
|
||||
}
|
||||
|
||||
function isPlainHostName (host) {
|
||||
return (host.search('\\.') == -1);
|
||||
}
|
||||
|
||||
function isResolvable (host) {
|
||||
var ip = dnsResolve(host);
|
||||
return (ip != null);
|
||||
}
|
||||
|
||||
function localHostOrDomainIs (host, hostdom) {
|
||||
return (host == hostdom) || (hostdom.lastIndexOf(host + '.', 0) == 0);
|
||||
}
|
||||
|
||||
function shExpMatch (url, pattern) {
|
||||
pattern = pattern.replace(/\./g, '\\.');
|
||||
pattern = pattern.replace(/\*/g, '.*');
|
||||
pattern = pattern.replace(/\?/g, '.');
|
||||
var newRe = new RegExp('^'+pattern+'$');
|
||||
return newRe.test(url);
|
||||
}
|
||||
|
||||
var wdays = {
|
||||
SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6
|
||||
};
|
||||
|
||||
var months = {
|
||||
JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11
|
||||
};
|
||||
|
||||
function weekdayRange () {
|
||||
function getDay (weekday) {
|
||||
if (weekday in wdays) {
|
||||
return wdays[weekday];
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
var date = new Date();
|
||||
var argc = arguments.length;
|
||||
var wday;
|
||||
if (argc < 1) {
|
||||
return false;
|
||||
}
|
||||
if (arguments[argc - 1] == 'GMT') {
|
||||
argc--;
|
||||
wday = date.getUTCDay();
|
||||
} else {
|
||||
wday = date.getDay();
|
||||
}
|
||||
var wd1 = getDay(arguments[0]);
|
||||
var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;
|
||||
return (wd1 == -1 || wd2 == -1) ? false : (wd1 <= wday && wday <= wd2);
|
||||
}
|
||||
|
||||
function dateRange () {
|
||||
function getMonth (name) {
|
||||
if (name in months) {
|
||||
return months[name];
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
var date = new Date();
|
||||
var argc = arguments.length;
|
||||
if (argc < 1) {
|
||||
return false;
|
||||
}
|
||||
var isGMT = (arguments[argc - 1] == 'GMT');
|
||||
if (isGMT) {
|
||||
argc--;
|
||||
}
|
||||
|
||||
// function will work even without explict handling of this case
|
||||
if (argc == 1) {
|
||||
var tmp = parseInt(arguments[0]);
|
||||
if (isNaN(tmp)) {
|
||||
return ((isGMT ? date.getUTCMonth() : date.getMonth()) == getMonth(arguments[0]));
|
||||
} else if (tmp < 32) {
|
||||
return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp);
|
||||
} else {
|
||||
return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) == tmp);
|
||||
}
|
||||
}
|
||||
|
||||
var year = date.getFullYear();
|
||||
var date1, date2;
|
||||
date1 = new Date(year, 0, 1, 0, 0, 0);
|
||||
date2 = new Date(year, 11, 31, 23, 59, 59);
|
||||
var adjustMonth = false;
|
||||
for (var i = 0; i < (argc >> 1); i++) {
|
||||
var tmp = parseInt(arguments[i]);
|
||||
if (isNaN(tmp)) {
|
||||
var mon = getMonth(arguments[i]);
|
||||
date1.setMonth(mon);
|
||||
} else if (tmp < 32) {
|
||||
adjustMonth = (argc <= 2);
|
||||
date1.setDate(tmp);
|
||||
} else {
|
||||
date1.setFullYear(tmp);
|
||||
}
|
||||
}
|
||||
for (var i = (argc >> 1); i < argc; i++) {
|
||||
var tmp = parseInt(arguments[i]);
|
||||
if (isNaN(tmp)) {
|
||||
var mon = getMonth(arguments[i]);
|
||||
date2.setMonth(mon);
|
||||
} else if(tmp < 32) {
|
||||
date2.setDate(tmp);
|
||||
} else {
|
||||
date2.setFullYear(tmp);
|
||||
}
|
||||
}
|
||||
if (adjustMonth) {
|
||||
date1.setMonth(date.getMonth());
|
||||
date2.setMonth(date.getMonth());
|
||||
}
|
||||
if (isGMT) {
|
||||
var tmp = date;
|
||||
tmp.setFullYear(date.getUTCFullYear());
|
||||
tmp.setMonth(date.getUTCMonth());
|
||||
tmp.setDate(date.getUTCDate());
|
||||
tmp.setHours(date.getUTCHours());
|
||||
tmp.setMinutes(date.getUTCMinutes());
|
||||
tmp.setSeconds(date.getUTCSeconds());
|
||||
date = tmp;
|
||||
}
|
||||
return ((date1 <= date) && (date <= date2));
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
//
|
||||
// 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.StringReader;
|
||||
import java.net.URL;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ProxyUtilTest {
|
||||
|
||||
static String[] strs (String... strs) { return strs; }
|
||||
|
||||
static String[] findPAC (String code, String url) throws Exception {
|
||||
return ProxyUtil.findPACProxiesForURL(new StringReader(code), new URL(url));
|
||||
}
|
||||
|
||||
static void testPAC (String code, String url, String... expectedProxies) throws Exception {
|
||||
assertArrayEquals(expectedProxies, findPAC(code, url));
|
||||
}
|
||||
|
||||
@Test public void testPACProxy () throws Exception {
|
||||
String EXAMPLE0 =
|
||||
"function FindProxyForURL(url, host) {\n" +
|
||||
" if (shExpMatch(host, '*.example.com')) { return 'DIRECT'; }\n" +
|
||||
" if (isInNet(host, '10.0.0.0', '255.255.248.0')) {\n" +
|
||||
" return 'PROXY fastproxy.example.com:8080';\n" +
|
||||
" }\n" +
|
||||
" return 'PROXY proxy.example.com:8080; DIRECT';\n" +
|
||||
"}\n";
|
||||
|
||||
testPAC(EXAMPLE0, "http://test.example.com/", "DIRECT");
|
||||
testPAC(EXAMPLE0, "http://10.0.1.1/", "PROXY fastproxy.example.com:8080");
|
||||
testPAC(EXAMPLE0, "http://chicken.gov/", "PROXY proxy.example.com:8080", "DIRECT");
|
||||
|
||||
String EXAMPLE1 =
|
||||
"function FindProxyForURL(url, host) {" +
|
||||
" if (isPlainHostName(host) || dnsDomainIs(host, '.mozilla.org')) {" +
|
||||
" return 'DIRECT';" +
|
||||
" } else {" +
|
||||
" return 'PROXY w3proxy.mozilla.org:8080; DIRECT';" +
|
||||
" }" +
|
||||
"}";
|
||||
testPAC(EXAMPLE1, "http://test.example.com/", "PROXY w3proxy.mozilla.org:8080", "DIRECT");
|
||||
testPAC(EXAMPLE1, "http://www.mozilla.org/", "DIRECT");
|
||||
testPAC(EXAMPLE1, "http://foo.mozilla.org/", "DIRECT");
|
||||
testPAC(EXAMPLE1, "http://localhost/", "DIRECT");
|
||||
|
||||
String EXAMPLE2 =
|
||||
" function FindProxyForURL(url, host) {\n" +
|
||||
" if ((isPlainHostName(host) || dnsDomainIs(host, '.mozilla.org')) &&\n" +
|
||||
" !localHostOrDomainIs(host, 'www.mozilla.org') &&\n" +
|
||||
" !localHostOrDomainIs(host, 'merchant.mozilla.org')) {\n" +
|
||||
" return 'DIRECT';\n" +
|
||||
" } else {\n" +
|
||||
" return 'PROXY w3proxy.mozilla.org:8080; DIRECT';\n" +
|
||||
" }\n" +
|
||||
" }";
|
||||
testPAC(EXAMPLE2, "http://test.example.com/", "PROXY w3proxy.mozilla.org:8080", "DIRECT");
|
||||
testPAC(EXAMPLE2, "http://www.mozilla.org/", "PROXY w3proxy.mozilla.org:8080", "DIRECT");
|
||||
testPAC(EXAMPLE2, "http://www/", "PROXY w3proxy.mozilla.org:8080", "DIRECT");
|
||||
testPAC(EXAMPLE2, "http://foo.mozilla.org/", "DIRECT");
|
||||
testPAC(EXAMPLE2, "http://localhost/", "DIRECT");
|
||||
|
||||
String EXAMPLE3A =
|
||||
"function FindProxyForURL(url, host) {\n" +
|
||||
" if (isResolvable(host)) return 'DIRECT';\n" +
|
||||
" else return 'PROXY proxy.mydomain.com:8080';\n" +
|
||||
"}";
|
||||
testPAC(EXAMPLE3A, "http://www.mozilla.org/", "DIRECT");
|
||||
testPAC(EXAMPLE3A, "http://doesnotexist.mozilla.org/", "PROXY proxy.mydomain.com:8080");
|
||||
|
||||
String EXAMPLE3B =
|
||||
"function FindProxyForURL(url, host) {\n" +
|
||||
" if (isPlainHostName(host) ||\n" +
|
||||
" dnsDomainIs(host, '.mydomain.com') ||\n" +
|
||||
" isResolvable(host)) {\n" +
|
||||
" return 'DIRECT';\n" +
|
||||
" } else {\n" +
|
||||
" return 'PROXY proxy.mydomain.com:8080';\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
testPAC(EXAMPLE3B, "http://plain/", "DIRECT");
|
||||
testPAC(EXAMPLE3B, "http://foo.mydomain.com/", "DIRECT");
|
||||
testPAC(EXAMPLE3B, "http://www.mozilla.org/", "DIRECT");
|
||||
testPAC(EXAMPLE3B, "http://doesnotexist.mozilla.org/", "PROXY proxy.mydomain.com:8080");
|
||||
|
||||
// example 4
|
||||
// function FindProxyForURL(url, host) {
|
||||
// if (isInNet(host, '198.95.0.0', '255.255.0.0')) return 'DIRECT';
|
||||
// else return 'PROXY proxy.mydomain.com:8080';
|
||||
// }
|
||||
// function FindProxyForURL(url, host) {
|
||||
// if (isPlainHostName(host) ||
|
||||
// dnsDomainIs(host, '.mydomain.com') ||
|
||||
// isInNet(host, '198.95.0.0', '255.255.0.0')) {
|
||||
// return 'DIRECT';
|
||||
// } else {
|
||||
// return 'PROXY proxy.mydomain.com:8080';
|
||||
// }
|
||||
// }
|
||||
|
||||
// example 5
|
||||
// function FindProxyForURL(url, host) {
|
||||
// if (isPlainHostName(host) || dnsDomainIs(host, '.mydomain.com')) return 'DIRECT';
|
||||
// else if (shExpMatch(host, '*.com')) return 'PROXY proxy1.mydomain.com:8080; ' +
|
||||
// 'PROXY proxy4.mydomain.com:8080';
|
||||
// else if (shExpMatch(host, '*.edu')) return 'PROXY proxy2.mydomain.com:8080; ' +
|
||||
// 'PROXY proxy4.mydomain.com:8080';
|
||||
// else return 'PROXY proxy3.mydomain.com:8080; ' +
|
||||
// 'PROXY proxy4.mydomain.com:8080';
|
||||
// }
|
||||
|
||||
// example 6
|
||||
// function FindProxyForURL(url, host) {
|
||||
// if (url.substring(0, 5) == 'http:') {
|
||||
// return 'PROXY http-proxy.mydomain.com:8080';
|
||||
// }
|
||||
// else if (url.substring(0, 4) == 'ftp:') {
|
||||
// return 'PROXY ftp-proxy.mydomain.com:8080';
|
||||
// }
|
||||
// else if (url.substring(0, 7) == 'gopher:') {
|
||||
// return 'PROXY gopher-proxy.mydomain.com:8080';
|
||||
// }
|
||||
// else if (url.substring(0, 6) == 'https:' ||
|
||||
// url.substring(0, 6) == 'snews:') {
|
||||
// return 'PROXY security-proxy.mydomain.com:8080';
|
||||
// } else {
|
||||
// return 'DIRECT';
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user