@capgo/inappbrowser 8.2.0 → 8.3.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -28
- package/android/src/main/assets/proxy-bridge.js +197 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/InAppBrowserPlugin.java +29 -26
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/Options.java +5 -7
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/ProxyBridge.java +60 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewCallbacks.java +2 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java +262 -165
- package/dist/docs.json +150 -3
- package/dist/esm/definitions.d.ts +65 -3
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.d.ts +20 -2
- package/dist/esm/index.js +79 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +1 -0
- package/dist/esm/web.js +4 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +83 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +83 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/InAppBrowserPlugin/InAppBrowserPlugin.swift +64 -1
- package/ios/Sources/InAppBrowserPlugin/ProxySchemeHandler.swift +233 -0
- package/ios/Sources/InAppBrowserPlugin/WKWebView+SchemeHandling.swift +53 -0
- package/ios/Sources/InAppBrowserPlugin/WKWebViewController.swift +18 -1
- package/package.json +4 -2
|
@@ -66,11 +66,14 @@ import com.caverock.androidsvg.SVG;
|
|
|
66
66
|
import com.caverock.androidsvg.SVGParseException;
|
|
67
67
|
import com.getcapacitor.JSObject;
|
|
68
68
|
import java.io.ByteArrayInputStream;
|
|
69
|
+
import java.io.ByteArrayOutputStream;
|
|
69
70
|
import java.io.File;
|
|
70
71
|
import java.io.IOException;
|
|
71
72
|
import java.io.InputStream;
|
|
73
|
+
import java.net.HttpURLConnection;
|
|
72
74
|
import java.net.URI;
|
|
73
75
|
import java.net.URISyntaxException;
|
|
76
|
+
import java.net.URL;
|
|
74
77
|
import java.nio.charset.StandardCharsets;
|
|
75
78
|
import java.security.PrivateKey;
|
|
76
79
|
import java.security.cert.X509Certificate;
|
|
@@ -85,7 +88,6 @@ import java.util.concurrent.ExecutorService;
|
|
|
85
88
|
import java.util.concurrent.Executors;
|
|
86
89
|
import java.util.concurrent.Semaphore;
|
|
87
90
|
import java.util.concurrent.TimeUnit;
|
|
88
|
-
import java.util.regex.Matcher;
|
|
89
91
|
import java.util.regex.Pattern;
|
|
90
92
|
import org.json.JSONException;
|
|
91
93
|
import org.json.JSONObject;
|
|
@@ -96,6 +98,12 @@ public class WebViewDialog extends Dialog {
|
|
|
96
98
|
|
|
97
99
|
private WebResourceResponse response;
|
|
98
100
|
private final Semaphore semaphore;
|
|
101
|
+
// Non-null when request came via JS proxy bridge (_capgo_proxy_ rewrite).
|
|
102
|
+
// Used by handleProxyResponse to do a native pass-through fetch when
|
|
103
|
+
// the handler returns null, since the rewritten URL is not loadable.
|
|
104
|
+
private String bridgedOriginalUrl;
|
|
105
|
+
private String bridgedMethod;
|
|
106
|
+
private Map<String, String> bridgedHeaders;
|
|
99
107
|
|
|
100
108
|
public WebResourceResponse getResponse() {
|
|
101
109
|
return response;
|
|
@@ -117,6 +125,9 @@ public class WebViewDialog extends Dialog {
|
|
|
117
125
|
private final WebView capacitorWebView;
|
|
118
126
|
private String instanceId = "";
|
|
119
127
|
private final Map<String, ProxiedRequest> proxiedRequestsHashmap = new HashMap<>();
|
|
128
|
+
private ProxyBridge proxyBridge;
|
|
129
|
+
private String proxyBridgeScript;
|
|
130
|
+
private String proxyAccessToken;
|
|
120
131
|
private final ExecutorService executorService = Executors.newCachedThreadPool();
|
|
121
132
|
private int iconColor = Color.BLACK; // Default icon color
|
|
122
133
|
private boolean isHiddenModeActive = false;
|
|
@@ -431,6 +442,13 @@ public class WebViewDialog extends Dialog {
|
|
|
431
442
|
_webView.addJavascriptInterface(new JavaScriptInterface(), "mobileApp");
|
|
432
443
|
_webView.addJavascriptInterface(new PreShowScriptInterface(), "PreShowScriptInterface");
|
|
433
444
|
_webView.addJavascriptInterface(new PrintInterface(this._context, _webView), "PrintInterface");
|
|
445
|
+
|
|
446
|
+
if (_options.getProxyRequests()) {
|
|
447
|
+
proxyAccessToken = UUID.randomUUID().toString();
|
|
448
|
+
proxyBridge = new ProxyBridge(proxyAccessToken);
|
|
449
|
+
_webView.addJavascriptInterface(proxyBridge, "__capgoProxy");
|
|
450
|
+
proxyBridgeScript = loadProxyBridgeScript();
|
|
451
|
+
}
|
|
434
452
|
_webView.getSettings().setJavaScriptEnabled(true);
|
|
435
453
|
_webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
|
|
436
454
|
_webView.getSettings().setDatabaseEnabled(true);
|
|
@@ -2182,74 +2200,6 @@ public class WebViewDialog extends Dialog {
|
|
|
2182
2200
|
buttonNearDoneView.setColorFilter(iconColor);
|
|
2183
2201
|
}
|
|
2184
2202
|
|
|
2185
|
-
public void handleProxyResultError(String result, String id) {
|
|
2186
|
-
Log.i("InAppBrowserProxy", String.format("handleProxyResultError: %s, ok: %s id: %s", result, false, id));
|
|
2187
|
-
ProxiedRequest proxiedRequest = proxiedRequestsHashmap.get(id);
|
|
2188
|
-
if (proxiedRequest == null) {
|
|
2189
|
-
Log.e("InAppBrowserProxy", "proxiedRequest is null");
|
|
2190
|
-
return;
|
|
2191
|
-
}
|
|
2192
|
-
proxiedRequestsHashmap.remove(id);
|
|
2193
|
-
proxiedRequest.semaphore.release();
|
|
2194
|
-
}
|
|
2195
|
-
|
|
2196
|
-
public void handleProxyResultOk(JSONObject result, String id) {
|
|
2197
|
-
Log.i("InAppBrowserProxy", String.format("handleProxyResultOk: %s, ok: %s, id: %s", result, true, id));
|
|
2198
|
-
ProxiedRequest proxiedRequest = proxiedRequestsHashmap.get(id);
|
|
2199
|
-
if (proxiedRequest == null) {
|
|
2200
|
-
Log.e("InAppBrowserProxy", "proxiedRequest is null");
|
|
2201
|
-
return;
|
|
2202
|
-
}
|
|
2203
|
-
proxiedRequestsHashmap.remove(id);
|
|
2204
|
-
|
|
2205
|
-
if (result == null) {
|
|
2206
|
-
proxiedRequest.semaphore.release();
|
|
2207
|
-
return;
|
|
2208
|
-
}
|
|
2209
|
-
|
|
2210
|
-
Map<String, String> responseHeaders = new HashMap<>();
|
|
2211
|
-
String body;
|
|
2212
|
-
int code;
|
|
2213
|
-
|
|
2214
|
-
try {
|
|
2215
|
-
body = result.getString("body");
|
|
2216
|
-
code = result.getInt("code");
|
|
2217
|
-
JSONObject headers = result.getJSONObject("headers");
|
|
2218
|
-
for (Iterator<String> it = headers.keys(); it.hasNext(); ) {
|
|
2219
|
-
String headerName = it.next();
|
|
2220
|
-
String header = headers.getString(headerName);
|
|
2221
|
-
responseHeaders.put(headerName, header);
|
|
2222
|
-
}
|
|
2223
|
-
} catch (JSONException e) {
|
|
2224
|
-
Log.e("InAppBrowserProxy", "Cannot parse OK result", e);
|
|
2225
|
-
return;
|
|
2226
|
-
}
|
|
2227
|
-
|
|
2228
|
-
String contentType = responseHeaders.get("Content-Type");
|
|
2229
|
-
if (contentType == null) {
|
|
2230
|
-
contentType = responseHeaders.get("content-type");
|
|
2231
|
-
}
|
|
2232
|
-
if (contentType == null) {
|
|
2233
|
-
Log.e("InAppBrowserProxy", "'Content-Type' header is required");
|
|
2234
|
-
return;
|
|
2235
|
-
}
|
|
2236
|
-
|
|
2237
|
-
if (!((100 <= code && code <= 299) || (400 <= code && code <= 599))) {
|
|
2238
|
-
Log.e("InAppBrowserProxy", String.format("Status code %s outside of the allowed range", code));
|
|
2239
|
-
return;
|
|
2240
|
-
}
|
|
2241
|
-
|
|
2242
|
-
WebResourceResponse webResourceResponse = new WebResourceResponse(
|
|
2243
|
-
contentType,
|
|
2244
|
-
"utf-8",
|
|
2245
|
-
new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8))
|
|
2246
|
-
);
|
|
2247
|
-
|
|
2248
|
-
webResourceResponse.setStatusCodeAndReasonPhrase(code, getReasonPhrase(code));
|
|
2249
|
-
proxiedRequest.response = webResourceResponse;
|
|
2250
|
-
proxiedRequest.semaphore.release();
|
|
2251
|
-
}
|
|
2252
|
-
|
|
2253
2203
|
private void setWebViewClient() {
|
|
2254
2204
|
_webView.setWebViewClient(
|
|
2255
2205
|
new WebViewClient() {
|
|
@@ -2550,18 +2500,6 @@ public class WebViewDialog extends Dialog {
|
|
|
2550
2500
|
}
|
|
2551
2501
|
}
|
|
2552
2502
|
|
|
2553
|
-
private String randomRequestId() {
|
|
2554
|
-
return UUID.randomUUID().toString();
|
|
2555
|
-
}
|
|
2556
|
-
|
|
2557
|
-
private String toBase64(String raw) {
|
|
2558
|
-
String s = Base64.encodeToString(raw.getBytes(), Base64.NO_WRAP);
|
|
2559
|
-
if (s.endsWith("=")) {
|
|
2560
|
-
s = s.substring(0, s.length() - 2);
|
|
2561
|
-
}
|
|
2562
|
-
return s;
|
|
2563
|
-
}
|
|
2564
|
-
|
|
2565
2503
|
//
|
|
2566
2504
|
// void handleRedirect(String currentUrl, Response response) {
|
|
2567
2505
|
// String loc = response.header("Location");
|
|
@@ -2573,104 +2511,98 @@ public class WebViewDialog extends Dialog {
|
|
|
2573
2511
|
if (view == null || _webView == null) {
|
|
2574
2512
|
return null;
|
|
2575
2513
|
}
|
|
2576
|
-
|
|
2577
|
-
if (pattern == null) {
|
|
2578
|
-
return null;
|
|
2579
|
-
}
|
|
2580
|
-
Matcher matcher = pattern.matcher(request.getUrl().toString());
|
|
2581
|
-
if (!matcher.find()) {
|
|
2514
|
+
if (!_options.getProxyRequests()) {
|
|
2582
2515
|
return null;
|
|
2583
2516
|
}
|
|
2584
2517
|
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2518
|
+
String requestUrl = request.getUrl().toString();
|
|
2519
|
+
String originalUrl;
|
|
2520
|
+
String method;
|
|
2521
|
+
String headersJson;
|
|
2522
|
+
String base64Body;
|
|
2590
2523
|
|
|
2591
|
-
|
|
2524
|
+
if (requestUrl.contains("/_capgo_proxy_?")) {
|
|
2525
|
+
// JS-patched fetch/XHR: extract original URL and stored request data
|
|
2526
|
+
Uri uri = request.getUrl();
|
|
2527
|
+
originalUrl = uri.getQueryParameter("u");
|
|
2528
|
+
String requestId = uri.getQueryParameter("rid");
|
|
2529
|
+
if (originalUrl == null || requestId == null) {
|
|
2530
|
+
return null;
|
|
2531
|
+
}
|
|
2592
2532
|
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2533
|
+
ProxyBridge.StoredRequest stored = proxyBridge != null ? proxyBridge.getAndRemove(requestId) : null;
|
|
2534
|
+
method = stored != null ? stored.method : "GET";
|
|
2535
|
+
headersJson = stored != null ? stored.headersJson : "{}";
|
|
2536
|
+
base64Body = stored != null ? stored.base64Body : "";
|
|
2537
|
+
} else {
|
|
2538
|
+
// Direct resource load (img, link, script, etc.) — extract from WebResourceRequest
|
|
2539
|
+
// Note: WebResourceRequest does not expose the request body, so for non-GET/HEAD
|
|
2540
|
+
// methods (e.g. form POST) the base64Body will be empty. This is an Android
|
|
2541
|
+
// platform limitation — the proxy handler receives the URL/headers/method but
|
|
2542
|
+
// must re-fetch the body if needed. In practice, direct resource loads from HTML
|
|
2543
|
+
// (img, link, script, iframe) are always GET.
|
|
2544
|
+
String scheme = request.getUrl().getScheme();
|
|
2545
|
+
if (scheme == null || (!scheme.equals("http") && !scheme.equals("https"))) {
|
|
2546
|
+
return null;
|
|
2547
|
+
}
|
|
2597
2548
|
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2549
|
+
originalUrl = requestUrl;
|
|
2550
|
+
method = request.getMethod();
|
|
2551
|
+
|
|
2552
|
+
// Convert request headers to JSON
|
|
2553
|
+
Map<String, String> reqHeaders = request.getRequestHeaders();
|
|
2554
|
+
JSONObject headersObj = new JSONObject();
|
|
2555
|
+
if (reqHeaders != null) {
|
|
2556
|
+
for (Map.Entry<String, String> entry : reqHeaders.entrySet()) {
|
|
2557
|
+
try {
|
|
2558
|
+
headersObj.put(entry.getKey(), entry.getValue());
|
|
2559
|
+
} catch (JSONException e) {
|
|
2560
|
+
// skip malformed header
|
|
2609
2561
|
}
|
|
2610
|
-
String jsTemplate = """
|
|
2611
|
-
try {
|
|
2612
|
-
function getHeaders() {
|
|
2613
|
-
const h = {};
|
|
2614
|
-
%s
|
|
2615
|
-
return h;
|
|
2616
|
-
}
|
|
2617
|
-
window.InAppBrowserProxyRequest(new Request(atob('%s'), {
|
|
2618
|
-
headers: getHeaders(),
|
|
2619
|
-
method: '%s'
|
|
2620
|
-
})).then(async (res) => {
|
|
2621
|
-
Capacitor.Plugins.InAppBrowser.lsuakdchgbbaHandleProxiedRequest({
|
|
2622
|
-
ok: true,
|
|
2623
|
-
result: (!!res ? {
|
|
2624
|
-
headers: Object.fromEntries(res.headers.entries()),
|
|
2625
|
-
code: res.status,
|
|
2626
|
-
body: (await res.text())
|
|
2627
|
-
} : null),
|
|
2628
|
-
id: '%s',
|
|
2629
|
-
webviewId: '%s'
|
|
2630
|
-
});
|
|
2631
|
-
}).catch((e) => {
|
|
2632
|
-
Capacitor.Plugins.InAppBrowser.lsuakdchgbbaHandleProxiedRequest({
|
|
2633
|
-
ok: false,
|
|
2634
|
-
result: e.toString(),
|
|
2635
|
-
id: '%s',
|
|
2636
|
-
webviewId: '%s'
|
|
2637
|
-
});
|
|
2638
|
-
});
|
|
2639
|
-
} catch (e) {
|
|
2640
|
-
Capacitor.Plugins.InAppBrowser.lsuakdchgbbaHandleProxiedRequest({
|
|
2641
|
-
ok: false,
|
|
2642
|
-
result: e.toString(),
|
|
2643
|
-
id: '%s',
|
|
2644
|
-
webviewId: '%s'
|
|
2645
|
-
});
|
|
2646
|
-
}
|
|
2647
|
-
""";
|
|
2648
|
-
String dialogId = instanceId != null ? instanceId : "";
|
|
2649
|
-
String s = String.format(
|
|
2650
|
-
jsTemplate,
|
|
2651
|
-
headers,
|
|
2652
|
-
toBase64(request.getUrl().toString()),
|
|
2653
|
-
request.getMethod(),
|
|
2654
|
-
requestId,
|
|
2655
|
-
dialogId,
|
|
2656
|
-
requestId,
|
|
2657
|
-
dialogId,
|
|
2658
|
-
requestId,
|
|
2659
|
-
dialogId
|
|
2660
|
-
);
|
|
2661
|
-
// Log.i("HTTP", s);
|
|
2662
|
-
capacitorWebView.evaluateJavascript(s, null);
|
|
2663
2562
|
}
|
|
2664
2563
|
}
|
|
2665
|
-
|
|
2564
|
+
headersJson = headersObj.toString();
|
|
2565
|
+
base64Body = "";
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
// Create a new requestId for the semaphore wait
|
|
2569
|
+
String proxyId = UUID.randomUUID().toString();
|
|
2570
|
+
ProxiedRequest proxiedRequest = new ProxiedRequest();
|
|
2571
|
+
|
|
2572
|
+
// For bridged requests, store original URL so null response
|
|
2573
|
+
// can do a native pass-through instead of loading the /_capgo_proxy_ URL
|
|
2574
|
+
if (requestUrl.contains("/_capgo_proxy_?")) {
|
|
2575
|
+
proxiedRequest.bridgedOriginalUrl = originalUrl;
|
|
2576
|
+
proxiedRequest.bridgedMethod = method;
|
|
2577
|
+
try {
|
|
2578
|
+
JSONObject hdr = new JSONObject(headersJson);
|
|
2579
|
+
Map<String, String> hdrMap = new HashMap<>();
|
|
2580
|
+
Iterator<String> keys = hdr.keys();
|
|
2581
|
+
while (keys.hasNext()) {
|
|
2582
|
+
String k = keys.next();
|
|
2583
|
+
hdrMap.put(k, hdr.getString(k));
|
|
2584
|
+
}
|
|
2585
|
+
proxiedRequest.bridgedHeaders = hdrMap;
|
|
2586
|
+
} catch (JSONException e) {
|
|
2587
|
+
proxiedRequest.bridgedHeaders = new HashMap<>();
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2666
2590
|
|
|
2667
|
-
|
|
2591
|
+
addProxiedRequest(proxyId, proxiedRequest);
|
|
2592
|
+
|
|
2593
|
+
// Fire proxyRequest event to the Capacitor plugin
|
|
2594
|
+
String dialogId = instanceId != null ? instanceId : "";
|
|
2595
|
+
_options
|
|
2596
|
+
.getCallbacks()
|
|
2597
|
+
.proxyRequestEvent(proxyId, originalUrl, method, headersJson, base64Body.isEmpty() ? null : base64Body, dialogId);
|
|
2598
|
+
|
|
2599
|
+
// Wait for response (10 seconds max)
|
|
2668
2600
|
try {
|
|
2669
2601
|
if (proxiedRequest.semaphore.tryAcquire(1, 10, TimeUnit.SECONDS)) {
|
|
2670
2602
|
return proxiedRequest.response;
|
|
2671
2603
|
} else {
|
|
2672
|
-
Log.e("InAppBrowserProxy", "Semaphore timed out");
|
|
2673
|
-
removeProxiedRequest(
|
|
2604
|
+
Log.e("InAppBrowserProxy", "Semaphore timed out for: " + originalUrl);
|
|
2605
|
+
removeProxiedRequest(proxyId);
|
|
2674
2606
|
}
|
|
2675
2607
|
} catch (InterruptedException e) {
|
|
2676
2608
|
Log.e("InAppBrowserProxy", "Semaphore wait error", e);
|
|
@@ -2756,6 +2688,13 @@ public class WebViewDialog extends Dialog {
|
|
|
2756
2688
|
} catch (URISyntaxException e) {
|
|
2757
2689
|
// Do nothing
|
|
2758
2690
|
}
|
|
2691
|
+
|
|
2692
|
+
// Inject proxy bridge script early so fetch/XHR are patched before page JS runs
|
|
2693
|
+
if (_options.getProxyRequests() && proxyBridgeScript != null && proxyAccessToken != null) {
|
|
2694
|
+
// Embed the access token directly in the script to avoid exposing it on window
|
|
2695
|
+
String scriptWithToken = proxyBridgeScript.replace("___CAPGO_PROXY_TOKEN___", proxyAccessToken);
|
|
2696
|
+
view.evaluateJavascript(scriptWithToken, null);
|
|
2697
|
+
}
|
|
2759
2698
|
}
|
|
2760
2699
|
|
|
2761
2700
|
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
|
|
@@ -3114,6 +3053,164 @@ public class WebViewDialog extends Dialog {
|
|
|
3114
3053
|
}
|
|
3115
3054
|
}
|
|
3116
3055
|
|
|
3056
|
+
private String loadProxyBridgeScript() {
|
|
3057
|
+
try (InputStream is = _context.getAssets().open("proxy-bridge.js")) {
|
|
3058
|
+
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
|
3059
|
+
byte[] buffer = new byte[4096];
|
|
3060
|
+
int bytesRead;
|
|
3061
|
+
while ((bytesRead = is.read(buffer)) != -1) {
|
|
3062
|
+
result.write(buffer, 0, bytesRead);
|
|
3063
|
+
}
|
|
3064
|
+
return result.toString(StandardCharsets.UTF_8.name());
|
|
3065
|
+
} catch (IOException e) {
|
|
3066
|
+
Log.e("InAppBrowserProxy", "Failed to load proxy-bridge.js", e);
|
|
3067
|
+
return null;
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
public void handleProxyResponse(String requestId, JSObject response) {
|
|
3072
|
+
ProxiedRequest proxiedRequest;
|
|
3073
|
+
synchronized (proxiedRequestsHashmap) {
|
|
3074
|
+
proxiedRequest = proxiedRequestsHashmap.get(requestId);
|
|
3075
|
+
}
|
|
3076
|
+
if (proxiedRequest == null) {
|
|
3077
|
+
Log.e("InAppBrowserProxy", "No pending request for id: " + requestId);
|
|
3078
|
+
return;
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
if (response == null) {
|
|
3082
|
+
// null response = pass through
|
|
3083
|
+
if (proxiedRequest.bridgedOriginalUrl != null) {
|
|
3084
|
+
// Bridged fetch/XHR: URL was rewritten to /_capgo_proxy_, so WebView
|
|
3085
|
+
// can't load it directly. Do a native pass-through fetch instead.
|
|
3086
|
+
executeNativePassThrough(proxiedRequest);
|
|
3087
|
+
}
|
|
3088
|
+
// For direct resource loads the response stays null, which tells
|
|
3089
|
+
// shouldInterceptRequest to return null and let WebView load normally.
|
|
3090
|
+
synchronized (proxiedRequestsHashmap) {
|
|
3091
|
+
proxiedRequestsHashmap.remove(requestId);
|
|
3092
|
+
}
|
|
3093
|
+
proxiedRequest.semaphore.release();
|
|
3094
|
+
return;
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
try {
|
|
3098
|
+
String base64Body = response.getString("body");
|
|
3099
|
+
int status = response.getInteger("status", 200);
|
|
3100
|
+
JSObject headers = response.getJSObject("headers");
|
|
3101
|
+
|
|
3102
|
+
Map<String, String> responseHeaders = new HashMap<>();
|
|
3103
|
+
if (headers != null) {
|
|
3104
|
+
Iterator<String> keys = headers.keys();
|
|
3105
|
+
while (keys.hasNext()) {
|
|
3106
|
+
String key = keys.next();
|
|
3107
|
+
responseHeaders.put(key, headers.getString(key));
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
byte[] bodyBytes = (base64Body != null && !base64Body.isEmpty()) ? Base64.decode(base64Body, Base64.DEFAULT) : new byte[0];
|
|
3112
|
+
|
|
3113
|
+
String contentType = responseHeaders.get("content-type");
|
|
3114
|
+
if (contentType == null) {
|
|
3115
|
+
contentType = responseHeaders.get("Content-Type");
|
|
3116
|
+
}
|
|
3117
|
+
if (contentType == null) {
|
|
3118
|
+
contentType = "application/octet-stream";
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
if (status < 100 || status > 599) {
|
|
3122
|
+
Log.w("InAppBrowserProxy", "Invalid HTTP status " + status + ", defaulting to 200");
|
|
3123
|
+
status = 200;
|
|
3124
|
+
}
|
|
3125
|
+
String reasonPhrase = getReasonPhrase(status);
|
|
3126
|
+
if (reasonPhrase.isEmpty()) {
|
|
3127
|
+
reasonPhrase = "Unknown";
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
WebResourceResponse webResourceResponse = new WebResourceResponse(contentType, "utf-8", new ByteArrayInputStream(bodyBytes));
|
|
3131
|
+
webResourceResponse.setStatusCodeAndReasonPhrase(status, reasonPhrase);
|
|
3132
|
+
webResourceResponse.setResponseHeaders(responseHeaders);
|
|
3133
|
+
|
|
3134
|
+
proxiedRequest.response = webResourceResponse;
|
|
3135
|
+
} catch (Exception e) {
|
|
3136
|
+
Log.e("InAppBrowserProxy", "Error building proxy response", e);
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3139
|
+
synchronized (proxiedRequestsHashmap) {
|
|
3140
|
+
proxiedRequestsHashmap.remove(requestId);
|
|
3141
|
+
}
|
|
3142
|
+
proxiedRequest.semaphore.release();
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
/**
|
|
3146
|
+
* Performs a native HTTP request for bridged fetch/XHR pass-through.
|
|
3147
|
+
* Called when proxy handler returns null for a request whose URL was
|
|
3148
|
+
* rewritten to /_capgo_proxy_, so WebView can't load it directly.
|
|
3149
|
+
*/
|
|
3150
|
+
private void executeNativePassThrough(ProxiedRequest proxiedRequest) {
|
|
3151
|
+
try {
|
|
3152
|
+
URL url = new URL(proxiedRequest.bridgedOriginalUrl);
|
|
3153
|
+
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
|
3154
|
+
conn.setRequestMethod(proxiedRequest.bridgedMethod != null ? proxiedRequest.bridgedMethod : "GET");
|
|
3155
|
+
conn.setInstanceFollowRedirects(true);
|
|
3156
|
+
|
|
3157
|
+
if (proxiedRequest.bridgedHeaders != null) {
|
|
3158
|
+
for (Map.Entry<String, String> entry : proxiedRequest.bridgedHeaders.entrySet()) {
|
|
3159
|
+
conn.setRequestProperty(entry.getKey(), entry.getValue());
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
|
|
3163
|
+
int status = conn.getResponseCode();
|
|
3164
|
+
InputStream inputStream;
|
|
3165
|
+
try {
|
|
3166
|
+
inputStream = conn.getInputStream();
|
|
3167
|
+
} catch (IOException e) {
|
|
3168
|
+
inputStream = conn.getErrorStream();
|
|
3169
|
+
}
|
|
3170
|
+
|
|
3171
|
+
byte[] bodyBytes = new byte[0];
|
|
3172
|
+
if (inputStream != null) {
|
|
3173
|
+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
3174
|
+
byte[] buf = new byte[4096];
|
|
3175
|
+
int n;
|
|
3176
|
+
while ((n = inputStream.read(buf)) != -1) {
|
|
3177
|
+
baos.write(buf, 0, n);
|
|
3178
|
+
}
|
|
3179
|
+
inputStream.close();
|
|
3180
|
+
bodyBytes = baos.toByteArray();
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
Map<String, String> responseHeaders = new HashMap<>();
|
|
3184
|
+
for (Map.Entry<String, java.util.List<String>> entry : conn.getHeaderFields().entrySet()) {
|
|
3185
|
+
if (entry.getKey() != null && entry.getValue() != null && !entry.getValue().isEmpty()) {
|
|
3186
|
+
responseHeaders.put(entry.getKey(), entry.getValue().get(0));
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
String contentType = responseHeaders.get("Content-Type");
|
|
3191
|
+
if (contentType == null) {
|
|
3192
|
+
contentType = responseHeaders.get("content-type");
|
|
3193
|
+
}
|
|
3194
|
+
if (contentType == null) {
|
|
3195
|
+
contentType = "application/octet-stream";
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
String reasonPhrase = getReasonPhrase(status);
|
|
3199
|
+
if (reasonPhrase.isEmpty()) {
|
|
3200
|
+
reasonPhrase = "Unknown";
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
WebResourceResponse webResourceResponse = new WebResourceResponse(contentType, "utf-8", new ByteArrayInputStream(bodyBytes));
|
|
3204
|
+
webResourceResponse.setStatusCodeAndReasonPhrase(status, reasonPhrase);
|
|
3205
|
+
webResourceResponse.setResponseHeaders(responseHeaders);
|
|
3206
|
+
proxiedRequest.response = webResourceResponse;
|
|
3207
|
+
|
|
3208
|
+
conn.disconnect();
|
|
3209
|
+
} catch (Exception e) {
|
|
3210
|
+
Log.e("InAppBrowserProxy", "Native pass-through failed for: " + proxiedRequest.bridgedOriginalUrl, e);
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3117
3214
|
private void shareUrl() {
|
|
3118
3215
|
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
|
3119
3216
|
shareIntent.setType("text/plain");
|