33import org .apache .commons .io .FileUtils ;
44import org .apache .commons .io .IOUtils ;
55
6+ import org .json .JSONObject ;
7+
68import java .io .IOException ;
79import java .io .InputStream ;
10+ import java .io .OutputStream ;
811import java .io .BufferedReader ;
912import java .io .InputStreamReader ;
1013import java .io .File ;
1518import java .util .zip .GZIPInputStream ;
1619import java .util .zip .ZipException ;
1720
21+ import java .lang .StringBuilder ;
22+
1823class LocalBinary {
1924
20- private static final String BIN_URL = "https://www.browserstack.com/local-testing/downloads/binaries/" ;
25+ private String binaryFileName ;
2126
22- private String httpPath ;
27+ private String sourceUrl ;
2328
2429 private String binaryPath ;
2530
31+ private Boolean fallbackEnabled = false ;
32+
33+ private Throwable downloadFailureThrowable = null ;
34+
35+ private String key ;
36+
2637 private boolean isOSWindows ;
2738
2839 private final String orderedPaths [] = {
@@ -31,14 +42,30 @@ class LocalBinary {
3142 System .getProperty ("java.io.tmpdir" )
3243 };
3344
34- LocalBinary (String path ) throws LocalException {
45+ LocalBinary (String path , String key ) throws LocalException {
46+ this .key = key ;
3547 initialize ();
36- if (path != "" ) {
37- getBinaryOnPath (path );
38- } else {
39- getBinary ();
48+ downloadAndVerifyBinary (path );
49+ }
50+
51+ private void downloadAndVerifyBinary (String path ) throws LocalException {
52+ try {
53+ if (path != "" ) {
54+ getBinaryOnPath (path );
55+ } else {
56+ getBinary ();
57+ }
58+ checkBinary ();
59+ } catch (Throwable e ) {
60+ if (fallbackEnabled ) throw e ;
61+ File binary_file = new File (binaryPath );
62+ if (binary_file .exists ()) {
63+ binary_file .delete ();
64+ }
65+ fallbackEnabled = true ;
66+ downloadFailureThrowable = e ;
67+ downloadAndVerifyBinary (path );
4068 }
41- checkBinary ();
4269 }
4370
4471 private void initialize () throws LocalException {
@@ -65,8 +92,7 @@ private void initialize() throws LocalException {
6592 throw new LocalException ("Failed to detect OS type" );
6693 }
6794
68- String sourceURL = BIN_URL ;
69- httpPath = sourceURL + binFileName ;
95+ this .binaryFileName = binFileName ;
7096 }
7197
7298 private boolean isAlpine () {
@@ -167,8 +193,53 @@ private boolean makePath(String path) {
167193 }
168194 }
169195
196+ private void fetchSourceUrl () throws LocalException {
197+ if ((!fallbackEnabled && sourceUrl != null ) || (fallbackEnabled && downloadFailureThrowable == null )) {
198+ /* Retry because binary (from any of the endpoints) validation failed */
199+ return ;
200+ }
201+
202+ try {
203+ URL url = new URL ("https://local.browserstack.com/binary/api/v1/endpoint" );
204+ URLConnection connection = url .openConnection ();
205+
206+ connection .setDoOutput (true );
207+ connection .setRequestProperty ("Content-Type" , "application/json" );
208+ connection .setRequestProperty ("User-Agent" , "browserstack-local-java/" + Local .getPackageVersion ());
209+ connection .setRequestProperty ("Accept" , "application/json" );
210+ if (fallbackEnabled ) connection .setRequestProperty ("X-Local-Fallback-Cloudflare" , "true" );
211+
212+ String jsonInput = "{\" auth_token\" : \" " + key + (fallbackEnabled ? ("\" , \" error_message\" : \" " + downloadFailureThrowable .getMessage ()) + "\" " : "\" " ) + "}" ;
213+
214+ try (OutputStream os = connection .getOutputStream ()) {
215+ byte [] input = jsonInput .getBytes ("utf-8" );
216+ os .write (input , 0 , input .length );
217+ }
218+
219+ try (InputStream is = connection .getInputStream ();
220+ BufferedReader reader = new BufferedReader (new InputStreamReader (is , "utf-8" ))) {
221+ StringBuilder response = new StringBuilder ();
222+ String line ;
223+ while ((line = reader .readLine ()) != null ) {
224+ response .append (line .trim ());
225+ }
226+ String responseBody = response .toString ();
227+ JSONObject json = new JSONObject (responseBody );
228+ if (json .has ("error" )) {
229+ throw new Exception (json .getString ("error" ));
230+ }
231+ this .sourceUrl = json .getJSONObject ("data" ).getString ("endpoint" );
232+ if (fallbackEnabled ) downloadFailureThrowable = null ;
233+ }
234+ } catch (Throwable e ) {
235+ throw new LocalException ("Error trying to fetch the source URL: " + e .getMessage ());
236+ }
237+ }
238+
170239 private void downloadBinary (String destParentDir , Boolean custom ) throws LocalException {
171240 try {
241+ fetchSourceUrl ();
242+
172243 String source = destParentDir ;
173244 if (!custom ) {
174245 if (!new File (destParentDir ).exists ())
@@ -179,13 +250,13 @@ private void downloadBinary(String destParentDir, Boolean custom) throws LocalEx
179250 source += ".exe" ;
180251 }
181252 }
182- URL url = new URL (httpPath );
253+ URL url = new URL (sourceUrl + '/' + binaryFileName );
183254
184255 File f = new File (source );
185256 newCopyToFile (url , f );
186257
187258 changePermissions (binaryPath );
188- } catch (Exception e ) {
259+ } catch (Throwable e ) {
189260 throw new LocalException ("Error trying to download BrowserStackLocal binary: " + e .getMessage ());
190261 }
191262 }
@@ -235,3 +306,4 @@ private static void customCopyInputStreamToFile(InputStream stream, File file, U
235306 }
236307 }
237308}
309+
0 commit comments