Reputation: 1617
Does Android allow native apps to disable CORS security policies for http:// (not local/file) requests?
In my native app, a webview shows a remote html via http://, not on the local/file system. This seems to be CORS-restricted in the same way as within webbrowsers.
Worakround: A native-js bridge for ajax requests to cross-domains which do not have Access-Control-Allow-Origin: *
is my quick'n'dirt solution. (jsonp or server-side proxy is not an option because cookie+ip of client are checked by the webservice.)
Can this policy be disabled for inapp webviews?
Please let me know, if there is a simple flag for allowing js to bypass this restriction which limits the "native" app's webview.
Upvotes: 29
Views: 55233
Reputation: 12912
I am halfway there. Only thing missing is POST and PUT requests, that need more than just working around the options request:
fun interceptWebRequest(request: WebResourceRequest?): WebResourceResponse? {
try {
if (request == null || request.isForMainFrame) {
return null
}
if (request.url?.path?.contains("assets/icons/favicon") == true) {
try {
return WebResourceResponse("image/png", null, null)
} catch (e: Exception) {
e.printStackTrace()
}
}
if (request.url.toString().contains(serviceHost)) {
return null
}
Log.v(
"TW",
"interceptRequest mf:${request?.isForMainFrame.toString()} ${request.method} ${request?.url}"
)
// since we currently don't have a way to also post the body, we only handle GET, HEAD and OPTIONS requests
// see https://github.com/KonstantinSchubert/request_data_webviewclient for a possible solution
if (request.method.uppercase() != "GET" && request.method.uppercase() != "OPTIONS" && request.method.uppercase() != "HEAD") {
return null
}
val client = OkHttpClient()
val newRequestBuilder = Request.Builder()
.url(request.url.toString())
.method(request.method, null)
for ((key, value) in request.requestHeaders) {
Log.v("TW", "interceptRequest header:${key} – ${value}")
if (key == "User-Agent" || key == "Origin" || key == "Referer" || key == "Sec-Fetch-Mode") {
continue
}
newRequestBuilder.addHeader(key, value)
}
newRequestBuilder.header("User-Agent", "curl/7.64.1")
val newRequest = newRequestBuilder.build()
if (request.method.uppercase() == "OPTIONS") {
Log.v("TW", "OPTIONS request triggered")
return OptionsAllowResponse.build()
}
//-------------
Log.v("TW", "exec request ${request.url}")
client.newCall(newRequest).execute().use { response ->
Log.v("TW", "response ${response.code} ${response.message}")
val responseHeaders = response.headers.names()
.associateWith { response.headers(it)?.joinToString() }
.toMutableMap()
upsertKeyValue(responseHeaders, "Access-Control-Allow-Origin", "*")
upsertKeyValue(
responseHeaders,
"Access-Control-Allow-Methods",
"GET, POST, OPTIONS"
)
val contentType = response.header("Content-Type", "text/plain")
val contentEncoding = response.header("Content-Encoding", "utf-8")
val inputStream = ByteArrayInputStream(response.body?.bytes())
val reasonPhrase =
response.message.ifEmpty { "OK" } // provide a default value if the message is null or empty
return WebResourceResponse(
contentType,
contentEncoding,
response.code,
reasonPhrase,
responseHeaders,
inputStream
)
}
} catch (e: Exception) {
Log.e("WebViewRequestHandler", "Error in interceptWebRequest => Not intercepting", e)
return null
}
}
fun upsertKeyValue(
responseHeaders: MutableMap<String, String?>,
keyToChange: String,
value: String
): MutableMap<String, String?> {
val keyToChangeLower = keyToChange.lowercase()
for (key in responseHeaders.keys) {
if (key.lowercase() == keyToChangeLower) {
// Reassign old key
responseHeaders[key] = value
// Done
return responseHeaders
}
}
responseHeaders[keyToChange] = value
return responseHeaders
}
Upvotes: 0
Reputation: 29
A shorter version that helped me using c# with Xamarin is the following:
public override WebResourceResponse ShouldInterceptRequest(WebView view, IWebResourceRequest request)
{
if (request.Url.SchemeSpecificPart.StartsWith("//<domain>/"))
{
request.RequestHeaders.Add("Access-Control-Allow-Origin", "*");
}
return base.ShouldInterceptRequest(view, request);
}
This override is done for Android.Webkit.WebViewClient
, where <domain>
is the one that is blocked by CORS policy.
Upvotes: -2
Reputation: 2495
This is now possible as of Android API level 21. You can create an OPTIONS response like so:
public class OptionsAllowResponse {
static final SimpleDateFormat formatter = new SimpleDateFormat("E, dd MMM yyyy kk:mm:ss", Locale.US);
@TargetApi(21)
static WebResourceResponse build() {
Date date = new Date();
final String dateString = formatter.format(date);
Map<String, String> headers = new HashMap<String, String>() {{
put("Connection", "close");
put("Content-Type", "text/plain");
put("Date", dateString + " GMT");
put("Access-Control-Allow-Origin", /* your domain here */);
put("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS");
put("Access-Control-Max-Age", "600");
put("Access-Control-Allow-Credentials", "true");
put("Access-Control-Allow-Headers", "accept, authorization, Content-Type");
put("Via", "1.1 vegur");
}};
return new WebResourceResponse("text/plain", "UTF-8", 200, "OK", headers, null);
}
}
and then call it from your WebViewClient implementation as follows:
@Override
@TargetApi(21)
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
if (request.getMethod().equalsIgnoreCase("OPTIONS")) {
return OptionsAllowResponse.build();
}
return null;
}
This only works from API level 21, since the OPTIONS response requires inspecting the requested HTTP method from the WebResourceRequest, which is only available since API 21.
Upvotes: 21
Reputation: 891
AFAIK this is not possible, and believe me, I've tried many ways.
The best you can do is override resource loading. See Intercept and override HTTP-requests from WebView
Upvotes: 12