diff --git a/src/main/java/com/browserstack/local/Local.java b/src/main/java/com/browserstack/local/Local.java index 7669007..85f9827 100644 --- a/src/main/java/com/browserstack/local/Local.java +++ b/src/main/java/com/browserstack/local/Local.java @@ -55,9 +55,9 @@ public void start(Map options) throws Exception { startOptions = options; LocalBinary lb; if (options.get("binarypath") != null) { - lb = new LocalBinary(options.get("binarypath")); + lb = new LocalBinary(options.get("binarypath"), options.get("key")); } else { - lb = new LocalBinary(""); + lb = new LocalBinary("", options.get("key")); } binaryPath = lb.getBinaryPath(); @@ -109,9 +109,9 @@ public void stop() throws Exception { public void stop(Map options) throws Exception { LocalBinary lb; if (options.get("binarypath") != null) { - lb = new LocalBinary(options.get("binarypath")); + lb = new LocalBinary(options.get("binarypath"), options.get("key")); } else { - lb = new LocalBinary(""); + lb = new LocalBinary("", options.get("key")); } binaryPath = lb.getBinaryPath(); makeCommand(options, "stop"); diff --git a/src/main/java/com/browserstack/local/LocalBinary.java b/src/main/java/com/browserstack/local/LocalBinary.java index 6b7a0fb..e2f3f4b 100644 --- a/src/main/java/com/browserstack/local/LocalBinary.java +++ b/src/main/java/com/browserstack/local/LocalBinary.java @@ -3,8 +3,11 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; +import org.json.JSONObject; + import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.File; @@ -15,14 +18,22 @@ import java.util.zip.GZIPInputStream; import java.util.zip.ZipException; +import java.lang.StringBuilder; + class LocalBinary { - private static final String BIN_URL = "https://www.browserstack.com/local-testing/downloads/binaries/"; + private String binaryFileName; - private String httpPath; + private String sourceUrl; private String binaryPath; + private Boolean fallbackEnabled = false; + + private Throwable downloadFailureThrowable = null; + + private String key; + private boolean isOSWindows; private final String orderedPaths[] = { @@ -31,14 +42,30 @@ class LocalBinary { System.getProperty("java.io.tmpdir") }; - LocalBinary(String path) throws LocalException { + LocalBinary(String path, String key) throws LocalException { + this.key = key; initialize(); - if (path != "") { - getBinaryOnPath(path); - } else { - getBinary(); + downloadAndVerifyBinary(path); + } + + private void downloadAndVerifyBinary(String path) throws LocalException { + try { + if (path != "") { + getBinaryOnPath(path); + } else { + getBinary(); + } + checkBinary(); + } catch (Throwable e) { + if (fallbackEnabled) throw e; + File binary_file = new File(binaryPath); + if (binary_file.exists()) { + binary_file.delete(); + } + fallbackEnabled = true; + downloadFailureThrowable = e; + downloadAndVerifyBinary(path); } - checkBinary(); } private void initialize() throws LocalException { @@ -65,8 +92,7 @@ private void initialize() throws LocalException { throw new LocalException("Failed to detect OS type"); } - String sourceURL = BIN_URL; - httpPath = sourceURL + binFileName; + this.binaryFileName = binFileName; } private boolean isAlpine() { @@ -167,8 +193,53 @@ private boolean makePath(String path) { } } + private void fetchSourceUrl() throws LocalException { + if ((!fallbackEnabled && sourceUrl != null) || (fallbackEnabled && downloadFailureThrowable == null)) { + /* Retry because binary (from any of the endpoints) validation failed */ + return; + } + + try { + URL url = new URL("https://local.browserstack.com/binary/api/v1/endpoint"); + URLConnection connection = url.openConnection(); + + connection.setDoOutput(true); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("User-Agent", "browserstack-local-java/" + Local.getPackageVersion()); + connection.setRequestProperty("Accept", "application/json"); + if (fallbackEnabled) connection.setRequestProperty("X-Local-Fallback-Cloudflare", "true"); + + String jsonInput = "{\"auth_token\": \"" + key + (fallbackEnabled ? ("\", \"error_message\": \"" + downloadFailureThrowable.getMessage()) + "\"" : "\"") + "}"; + + try (OutputStream os = connection.getOutputStream()) { + byte[] input = jsonInput.getBytes("utf-8"); + os.write(input, 0, input.length); + } + + try (InputStream is = connection.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(is, "utf-8"))) { + StringBuilder response = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + response.append(line.trim()); + } + String responseBody = response.toString(); + JSONObject json = new JSONObject(responseBody); + if (json.has("error")) { + throw new Exception(json.getString("error")); + } + this.sourceUrl = json.getJSONObject("data").getString("endpoint"); + if(fallbackEnabled) downloadFailureThrowable = null; + } + } catch (Throwable e) { + throw new LocalException("Error trying to fetch the source URL: " + e.getMessage()); + } + } + private void downloadBinary(String destParentDir, Boolean custom) throws LocalException { try { + fetchSourceUrl(); + String source = destParentDir; if (!custom) { if (!new File(destParentDir).exists()) @@ -179,13 +250,13 @@ private void downloadBinary(String destParentDir, Boolean custom) throws LocalEx source += ".exe"; } } - URL url = new URL(httpPath); + URL url = new URL(sourceUrl + '/' + binaryFileName); File f = new File(source); newCopyToFile(url, f); changePermissions(binaryPath); - } catch (Exception e) { + } catch (Throwable e) { throw new LocalException("Error trying to download BrowserStackLocal binary: " + e.getMessage()); } }