mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Internet helper: Support forwarding POST requests
This commit is contained in:
+4
-1
@@ -52,6 +52,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.webview.GBChromeClient;
|
||||
import nodomain.freeyourgadget.gadgetbridge.webview.GBWebClient;
|
||||
import nodomain.freeyourgadget.gadgetbridge.webview.RequestInterceptorInterface;
|
||||
|
||||
public class ExternalPebbleJSActivity extends AbstractGBActivity {
|
||||
|
||||
@@ -115,7 +116,8 @@ public class ExternalPebbleJSActivity extends AbstractGBActivity {
|
||||
setContentView(R.layout.activity_legacy_external_pebble_js);
|
||||
myWebView = findViewById(R.id.configureWebview);
|
||||
myWebView.clearCache(true);
|
||||
myWebView.setWebViewClient(new GBWebClient(GBWebClient.REQUEST_TYPE_PEBBLE_APP_CONFIG));
|
||||
GBWebClient gbWebClient = new GBWebClient(GBWebClient.REQUEST_TYPE_PEBBLE_APP_CONFIG);
|
||||
myWebView.setWebViewClient(gbWebClient);
|
||||
myWebView.setWebChromeClient(new GBChromeClient());
|
||||
WebSettings webSettings = myWebView.getSettings();
|
||||
//noinspection SetJavaScriptEnabled
|
||||
@@ -132,6 +134,7 @@ public class ExternalPebbleJSActivity extends AbstractGBActivity {
|
||||
JSInterface gbJSInterface = new JSInterface(device, uuid);
|
||||
myWebView.addJavascriptInterface(gbJSInterface, "GBjs");
|
||||
myWebView.addJavascriptInterface(new ActivityJSInterface(), "GBActivity");
|
||||
myWebView.addJavascriptInterface(new RequestInterceptorInterface(gbWebClient), "GBReqInt");
|
||||
|
||||
myWebView.loadUrl("file:///android_asset/app_config/configure.html?rand=" + Math.random() * 500);
|
||||
}
|
||||
|
||||
+4
-1
@@ -38,6 +38,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.InternetUtils
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils
|
||||
import nodomain.freeyourgadget.gadgetbridge.webview.GBChromeClient
|
||||
import nodomain.freeyourgadget.gadgetbridge.webview.GBWebClient
|
||||
import nodomain.freeyourgadget.gadgetbridge.webview.RequestInterceptorInterface
|
||||
import org.json.JSONObject
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
@@ -135,7 +136,7 @@ class RebbleAppStoreActivity : AbstractGBActivity() {
|
||||
settings.loadWithOverviewMode = true
|
||||
settings.useWideViewPort = true
|
||||
|
||||
webView!!.webViewClient = object : GBWebClient(REQUEST_TYPE_PEBBLE_APP_STORE) {
|
||||
val gbWebClient = object : GBWebClient(REQUEST_TYPE_PEBBLE_APP_STORE) {
|
||||
override fun shouldOverrideUrlLoading(
|
||||
wv: WebView,
|
||||
request: WebResourceRequest
|
||||
@@ -170,6 +171,7 @@ class RebbleAppStoreActivity : AbstractGBActivity() {
|
||||
view.loadUrl("about:blank")
|
||||
}
|
||||
}
|
||||
webView!!.webViewClient = gbWebClient
|
||||
|
||||
webView!!.webChromeClient = object : GBChromeClient() {
|
||||
override fun onPermissionRequest(request: PermissionRequest) {
|
||||
@@ -177,6 +179,7 @@ class RebbleAppStoreActivity : AbstractGBActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
webView!!.addJavascriptInterface(RequestInterceptorInterface(gbWebClient), "GBReqInt")
|
||||
webView!!.loadUrl(url)
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -54,6 +54,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs.BangleJSDev
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.webview.GBChromeClient;
|
||||
import nodomain.freeyourgadget.gadgetbridge.webview.GBWebClient;
|
||||
import nodomain.freeyourgadget.gadgetbridge.webview.RequestInterceptorInterface;
|
||||
|
||||
public class AppsManagementActivity extends AbstractGBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AppsManagementActivity.class);
|
||||
@@ -218,7 +219,7 @@ public class AppsManagementActivity extends AbstractGBActivity {
|
||||
webView.addJavascriptInterface(new WebViewInterface(this), "Android");
|
||||
webView.setWebContentsDebuggingEnabled(true); // FIXME
|
||||
|
||||
webView.setWebViewClient(new GBWebClient(GBWebClient.REQUEST_TYPE_BANGLE_APP_LOADER){
|
||||
GBWebClient gbWebClient = new GBWebClient(GBWebClient.REQUEST_TYPE_BANGLE_APP_LOADER){
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String weburl){
|
||||
//webView.loadUrl("javascript:showToast('WebView in Espruino')");
|
||||
@@ -229,7 +230,10 @@ public class AppsManagementActivity extends AbstractGBActivity {
|
||||
Toast.makeText(AppsManagementActivity.this, "Error:" + description, Toast.LENGTH_SHORT).show();
|
||||
view.loadUrl("about:blank");
|
||||
}
|
||||
});
|
||||
};
|
||||
webView.setWebViewClient(gbWebClient);
|
||||
|
||||
webView.addJavascriptInterface(new RequestInterceptorInterface(gbWebClient), "GBReqInt");
|
||||
|
||||
Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(mGBDevice.getAddress()));
|
||||
String url = devicePrefs.getString(PREF_BANGLEJS_WEBVIEW_URL, "").trim();
|
||||
|
||||
+5
-2
@@ -33,6 +33,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB
|
||||
import nodomain.freeyourgadget.gadgetbridge.webview.GBChromeClient
|
||||
import nodomain.freeyourgadget.gadgetbridge.webview.GBWebClient
|
||||
import nodomain.freeyourgadget.gadgetbridge.webview.RequestInterceptorInterface
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.UUID
|
||||
@@ -102,6 +103,7 @@ class PebbleJsService : Service() {
|
||||
LOG.info("WEBVIEW starting for device ${device.address}")
|
||||
WebView.setWebContentsDebuggingEnabled(true)
|
||||
val uiContext = applicationContext.createConfigurationContext(resources.configuration)
|
||||
val gbWebClient = GBWebClient(GBWebClient.REQUEST_TYPE_PEBBLE_BACKGROUND_JS)
|
||||
val wv = WebView(uiContext).apply {
|
||||
settings.javaScriptEnabled = true
|
||||
settings.domStorageEnabled = true
|
||||
@@ -111,10 +113,11 @@ class PebbleJsService : Service() {
|
||||
settings.allowFileAccessFromFileURLs = true
|
||||
settings.allowUniversalAccessFromFileURLs = true
|
||||
visibility = View.GONE
|
||||
webViewClient = GBWebClient(GBWebClient.REQUEST_TYPE_PEBBLE_BACKGROUND_JS)
|
||||
webViewClient = gbWebClient
|
||||
webChromeClient = GBChromeClient()
|
||||
addJavascriptInterface(RequestInterceptorInterface(gbWebClient), "GBReqInt")
|
||||
setWillNotDraw(true)
|
||||
}
|
||||
wv.setWillNotDraw(true)
|
||||
wv.clearCache(true)
|
||||
wv.resumeTimers()
|
||||
webviews[device.address] = wv
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.webview;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
import android.webkit.WebResourceRequest;
|
||||
@@ -39,7 +40,10 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
@@ -65,11 +69,54 @@ public class GBWebClient extends WebViewClient {
|
||||
"rawgit.com", //for trekvolle
|
||||
};
|
||||
|
||||
private final Map<String, List<Entry>> postData = new HashMap<>();
|
||||
private record Entry(long timestamp, String body) {}
|
||||
|
||||
public GBWebClient(int type) {
|
||||
super();
|
||||
requestType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores HTTP POST body strings for later retrieval in mimicReply()
|
||||
*/
|
||||
public synchronized void storePostBody(String url, String body) {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
// Cleanup expired entries (more than a minute old)
|
||||
Iterator<Map.Entry<String, List<Entry>>> mapIt = postData.entrySet().iterator();
|
||||
while (mapIt.hasNext()) {
|
||||
Map.Entry<String, List<Entry>> mapEntry = mapIt.next();
|
||||
List<Entry> list = mapEntry.getValue();
|
||||
list.removeIf(e -> now - e.timestamp > 60*1000);
|
||||
if (list.isEmpty()) {
|
||||
mapIt.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Add new entry
|
||||
postData
|
||||
.computeIfAbsent(url, k -> new ArrayList<>())
|
||||
.add(new Entry(now, body));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the earliest known HTTP POST body for a certain URL
|
||||
*/
|
||||
private synchronized String getFirstPostForUrl(String url) {
|
||||
List<Entry> list = postData.get(url);
|
||||
if (list == null || list.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Entry first = list.remove(0);
|
||||
if (list.isEmpty()) {
|
||||
postData.remove(url);
|
||||
}
|
||||
|
||||
return first.body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
|
||||
Uri parsedUri = request.getUrl();
|
||||
@@ -135,7 +182,13 @@ public class GBWebClient extends WebViewClient {
|
||||
if (!forceLocal && !directInternetAccess && InternetHelperSingleton.INSTANCE.ensureInternetHelperBound()) {
|
||||
LOG.debug("WEBVIEW forwarding request to the internet helper");
|
||||
try {
|
||||
WebResourceResponse wrr = InternetHelperSingleton.INSTANCE.send(requestedUri, HttpRequest.Method.valueOf(method), requestHeaders, null, "application/octet-stream", false);
|
||||
HttpRequest.Method requestMethod = HttpRequest.Method.valueOf(method);
|
||||
String body = null;
|
||||
if (requestMethod == HttpRequest.Method.POST) {
|
||||
body = getFirstPostForUrl(requestedUri.toString());
|
||||
LOG.debug("WEBVIEW POSTing with body: {}", body);
|
||||
}
|
||||
WebResourceResponse wrr = InternetHelperSingleton.INSTANCE.send(requestedUri, requestMethod, requestHeaders, body, "application/octet-stream", false);
|
||||
if (wrr != null && wrr.getStatusCode() < 400)
|
||||
return wrr;
|
||||
else
|
||||
@@ -192,6 +245,57 @@ public class GBWebClient extends WebViewClient {
|
||||
return shouldOverrideUrlLoading(view, request.getUrl().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
injectPostInterceptor(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
injectPostInterceptor(view);
|
||||
}
|
||||
|
||||
private void injectPostInterceptor(WebView view) {
|
||||
// Inject JavaScript for capturing POST body data, which we need when forwarding
|
||||
// requests to the internet helper
|
||||
view.evaluateJavascript(
|
||||
"""
|
||||
(function () {
|
||||
if (window.__p) return; window.__p = 1;
|
||||
const sb = b => b instanceof FormData
|
||||
? [...b].map(e => e.map(encodeURIComponent).join('=')).join('&')
|
||||
: b ? b.toString() : null;
|
||||
|
||||
const send = (u, b) => GBReqInt.onPostBody(new URL(u, location.href).toString(), sb(b));
|
||||
|
||||
const xo = XMLHttpRequest.prototype.open;
|
||||
const xs = XMLHttpRequest.prototype.send;
|
||||
XMLHttpRequest.prototype.open = function (m, u) {
|
||||
this._m = m; this._u = u; return xo.apply(this, arguments);
|
||||
};
|
||||
XMLHttpRequest.prototype.send = function (b) {
|
||||
if (this._m === 'POST') send(this._u, b);
|
||||
return xs.apply(this, arguments);
|
||||
};
|
||||
|
||||
const f = window.fetch;
|
||||
window.fetch = function (i, o = {}) {
|
||||
if ((o.method || 'GET') === 'POST')
|
||||
send(typeof i === 'string' ? i : i.url, o.body);
|
||||
return f.apply(this, arguments);
|
||||
};
|
||||
|
||||
document.addEventListener('submit', e => {
|
||||
const f = e.target;
|
||||
if (f.method && f.method.toUpperCase() === 'POST')
|
||||
send(f.action || location.href, new FormData(f));
|
||||
}, true);
|
||||
})();
|
||||
""",
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
private WebResourceResponse mimicRawGitResponse(String path) {
|
||||
if("/aHcVolle/TrekVolle/master/online.html".equals(path)) { //TrekVolle online check
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/* Copyright (C) 2025 Arjan Schrijver
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.webview
|
||||
|
||||
import android.webkit.JavascriptInterface
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class RequestInterceptorInterface(val gbWebClient: GBWebClient) {
|
||||
val LOG: Logger = LoggerFactory.getLogger(RequestInterceptorInterface::class.java)
|
||||
|
||||
@JavascriptInterface
|
||||
fun onPostBody(url: String, body: String) {
|
||||
LOG.debug("POST_INTERCEPTED FOR {}, BODY: {}", url, body)
|
||||
gbWebClient.storePostBody(url, body)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user