Getting around X-Frame-Options DENY in an Android WebView - android

I am attempting to implement a technique similar to the one describe in this question.
I have an android application (Ionic built on top of Cordova) that runs in a webview. Basically what I want to do is load a page into an iframe and perform some work on this page. Many website uses the X-Frame-Options: DENY header to disallow their content from being loaded in an iFrame. In a chrome extension you can get around this by intercepting the webrequest and removing that header.
I've overridden the shouldInterceptRequest function here: https://developer.android.com/reference/android/webkit/WebViewClient.html
// Handle API until level 21
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
try {
WebResourceResponse cordovaResponse = super.shouldInterceptRequest(view, request);
if(cordovaResponse != null) {
return cordovaResponse;
}
String url = request.getUrl().toString();
HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.connect();
//view.loadUrl(url, getCustomHeaders());
WebResourceResponse response = new WebResourceResponse(urlConnection.getContentType(),
urlConnection.getContentEncoding(),
urlConnection.getInputStream());
Map<String, String> headers = response.getResponseHeaders();
if(headers != null){
response.setResponseHeaders(removeXOriginHeaders(headers));
}
return response;
} catch(MalformedURLException e) {
e.printStackTrace();
return null;
}
catch (IOException e) {
e.printStackTrace();
return null;
}
}`
but when the headers for all requests are received using the above method they are null and when the content is put into the iframe, it doesn't result in a fully formed Document.
The chrome debugger provides this message: Resource interpreted as Document but transferred with MIME type text/html;charset=UTF-8:
It's like the page content is fetched using xhr and then stuck inside a single element of the Document as opposed to loading as it normally would when using an iframe (all scripts run to execution, subsequent ajax requests fired etc).
Is there anyway to get the page content to load in the iframe after having removed that single header?

I was able to solve my problem by using the OkHttpClient found here: http://square.github.io/okhttp/ instead of the java URLConnection
// Handle API until level 21
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
try {
WebResourceResponse cordovaResponse = super.shouldInterceptRequest(view, request);
if(cordovaResponse != null) {
return cordovaResponse;
}
String url = request.getUrl().toString();
OkHttpClient httpClient = new OkHttpClient();
Request okRequest = new Request.Builder()
.url(url)
.build();
Response response = httpClient.newCall(okRequest).execute();
Response modifiedResponse = response.newBuilder()
.removeHeader("x-frame-options")
.removeHeader("frame-options")
.build();
return new WebResourceResponse("text/html",
modifiedResponse.header("content-encoding", "utf-8"),
modifiedResponse.body().byteStream()
);
} catch(MalformedURLException e) {
e.printStackTrace();
return null;
}
catch (IOException e) {
e.printStackTrace();
return null;
}
}

Related

Android WebView sometimes doesn't send request headers on initial page load

I have a webview activity that loads a URL with a few custom request headers in its onCreate() method. The requirement is to pass the custom headers with the initial URL request. On a few devices, the webview stops sending the headers after the webview activity has been launched a few times.
For example, I have a HomeActivity which launches a WebViewActivity. After launching the WebViewActivity and navigating back to HomeActivity a few times, the WebViewActivity stops sending the custom request headers and this behaviour doesn't change unless I clear the application's data.
I have confirmed this behaviour using a MITM tool. The implementation is as follows:
#Override
protected void onCreate(Bundle savedInstanceState) {
Map<String, String> map = new HashMap<>();
map.put("header1", "header1_value");
map.put("header2", "header2_value");
map.put("header3", "header3_value");
map.put("header4", "header4_value");
webView.loadUrl("https://www.example.com/mypath", map);
}
The above snippet executes unconditionally on every activity launch. However, the headers are not present in the actual request made by the webview. Also, the page being requested is a 303 redirect.
If your minimum API target is level 21, you can use the shouldInterceptRequest else you can use this
With each interception, you will need to take the url, make this request yourself, and return the content stream:
Then:
WebViewClient wvc = new WebViewClient() {
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
try {
DefaultHttpClient client = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("header1", "header1_value");
httpGet.setHeader("header2", "header2_value");
httpGet.setHeader("header3", "header3_value");
httpGet.setHeader("header4", "header4_value");
HttpResponse httpReponse = client.execute(httpGet);
Header contentType = httpReponse.getEntity().getContentType();
Header encoding = httpReponse.getEntity().getContentEncoding();
InputStream responseInputStream = httpReponse.getEntity().getContent();
String contentTypeValue = null;
String encodingValue = null;
if (contentType != null) {
contentTypeValue = contentType.getValue();
}
if (encoding != null) {
encodingValue = encoding.getValue();
}
return new WebResourceResponse(contentTypeValue, encodingValue, responseInputStream);
} catch (ClientProtocolException e) {
//return null to tell WebView we failed to fetch it WebView should try again.
return null;
} catch (IOException e) {
//return null to tell WebView we failed to fetch it WebView should try again.
return null;
}
}
}
//Where wv is your webview
wv.setWebViewClient(wvc);
Based on this question

Android WebView cache AJAX response for offline

Is there a way to cache the AJAX responses received in the WebView to be cached for offline use?
I have cached the page and all its resources using some of the webveiw settings and most importnant:
ConnectivityManager cm = (ConnectivityManager) getActivity().getSystemService(Activity.CONNECTIVITY_SERVICE);
if (cm.getActiveNetworkInfo() == null || !cm.getActiveNetworkInfo().isConnected()) {
wvContent.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
} else {
wvContent.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
}
But in my client's page, that I cached, there are some AJAX calls which responses I want co cache for offline use? Is there a way to achieve that?
In order to achieve this you have to implement two things.
1) a way to catch the Ajax response and cache it.
2) a way to serve the cached result on an ajax request.
Part #1
You can use a JavascriptInterface and jQuery to catch the AJAX response. Look here for an example how to use the JavascriptInterface. After that you can cache the response
Part #2 (or 1 & 2)
You can serve the cached content using the shouldInterceptRequest method of WebViewClient. Look here for an example. In the example you can combine the #part 1 and make a network call to fetch the Ajax response manually.
Here is an example you can work with.
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
Log.i(TAG,"shouldInterceptRequest path:"+request.getUrl().getPath());
WebResourceResponse returnResponse = null;
if (request.getUrl().getPath().startsWith("/cart")) { // only interested in /cart requests
returnResponse = super.shouldInterceptRequest(view, request);
Log.i(TAG,"cart AJAX call - doing okRequest");
Request okRequest = new Request.Builder()
.url(request.getUrl().toString())
.post(null)
.build();
try {
Response okResponse = app.getOkHttpClient().newCall(okRequest).execute();
if (okResponse!=null) {
int statusCode = okResponse.code();
String encoding = "UTF-8";
String mimeType = "application/json";
String reasonPhrase = "OK";
Map<String,String> responseHeaders = new HashMap<String,String>();
if (okResponse.headers()!=null) {
if (okResponse.headers().size()>0) {
for (int i = 0; i < okResponse.headers().size(); i++) {
String key = okResponse.headers().name(i);
String value = okResponse.headers().value(i);
responseHeaders.put(key, value);
if (key.toLowerCase().contains("x-cart-itemcount")) {
Log.i(TAG,"setting cart item count");
app.setCartItemsCount(Integer.parseInt(value));
}
}
}
}
InputStream data = new ByteArrayInputStream(okResponse.body().string().getBytes(StandardCharsets.UTF_8));
Log.i(TAG, "okResponse code:" + okResponse.code());
returnResponse = new WebResourceResponse(mimeType,encoding,statusCode,reasonPhrase,responseHeaders,data);
} else {
Log.w(TAG,"okResponse fail");
}
} catch (IOException e) {
e.printStackTrace();
}
}
return returnResponse;
}

CORS with Crosswalk and OKHttp

I'm trying to use a crosswalk embedded webview to display a web page, with some javascript. Because I need to add some headers to each request, I am intercepting the request with shouldInterceptLoadRequest, and making the request with OkHttp.
#Override
public WebResourceResponse shouldInterceptLoadRequest(XWalkView view, String url) {
try {
Log.i(App.TAG, url);
return new WebResourceResponse("", "UTF-8", getUrl(url));
} catch (Exception e) {
e.printStackTrace();
return super.shouldInterceptLoadRequest(view, url);
}
}
InputStream getUrl(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.addHeader("MyHeader","MyHeaderValue")
.build();
Response response = client.newCall(request).execute();
return response.body().byteStream();
}
This code works as intended at first, but upon making an Ajax request, I get this error : [INFO:CONSOLE(0)] "XMLHttpRequest cannot load https://api.example1.com. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://example2.com' is therefore not allowed access."
I do not get this error if I don't intercept the request, but then I loose the ability to add headers to the request.

Android - Webview only applying headers to initial request

I'm writing an android app that uses webview to request content from a web server, but using mWebView.loadUrl(url1, headers); will only apply the headers to the initial request and not the resources in the request.
Any idea as so how to apply the headers to the resource requests as well?
Not absolutely sure but you can try to override shouldOverrideUrlLoading(WebView view, String url) method and handle all redirects by starting mWebView.loadUrl(url, yourHeaders);
Dont forget to return true in that overriden method.
First of all, let me say that i can't believe that webview sucks so much.
This is what i did to pass custom headers
public class CustomWebview extends WebView {
public void loadWithHeaders(String url) {
setWebViewClient(new WebViewClient() {
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
//makes a custom http request, which allows you to add your own headers
return customRequest(url);
}
});
loadUrl(url);
}
/**
* Custom http request with headers
* #param url
* #return
*/
private WebResourceResponse customRequest(String url) {
try {
OkHttpClient httpClient = new OkHttpClient();
Request request = new Request.Builder()
.url(url.trim())
.addHeader("Header-Name", "Android Sucks")
.build();
Response response = httpClient.newCall(request).execute();
return new WebResourceResponse(
"text/html", // You can set something other as default content-type
"utf-8", // Again, you can set another encoding as default
response.body().byteStream()
);
} catch (IOException e) {
//return null to tell WebView we failed to fetch it WebView should try again.
return null;
}
}
}

Access the http response headers in a WebView?

Is there a way to view the http response headers in an Activity once a web page has been loaded in a WebView? Seems like this should be possible, but I can't find any methods that expose the headers.
Neither WebView nor WebViewClient provide methods to do that, Though, you can try to implement that manually. You can do something like this:
private WebView webview;
public void onCreate(Bundle icicle){
// bla bla bla
// here you initialize your webview
webview = new WebView(this);
webview.setWebViewClient(new YourWebClient());
}
// this will be the webclient that will manage the webview
private class YourWebClient extends WebViewClient{
// you want to catch when an URL is going to be loaded
public boolean shouldOverrideUrlLoading (WebView view, String urlConection){
// here you will use the url to access the headers.
// in this case, the Content-Length one
URL url;
URLConnection conexion;
try {
url = new URL(urlConection);
conexion = url.openConnection();
conexion.setConnectTimeout(3000);
conexion.connect();
// get the size of the file which is in the header of the request
int size = conexion.getContentLength();
}
// and here, if you want, you can load the page normally
String htmlContent = "";
HttpGet httpGet = new HttpGet(urlConection);
// this receives the response
HttpResponse response;
try {
response = httpClient.execute(httpGet);
if (response.getStatusLine().getStatusCode() == 200) {
// la conexion fue establecida, obtener el contenido
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream inputStream = entity.getContent();
htmlContent = convertToString(inputStream);
}
}
} catch (Exception e) {}
webview.loadData(htmlContent, "text/html", "utf-8");
return true;
}
public String convertToString(InputStream inputStream){
StringBuffer string = new StringBuffer();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
try {
while ((line = reader.readLine()) != null) {
string.append(linea + "\n");
}
} catch (IOException e) {}
return string.toString();
}
}
I can't test it right now, but that's basically what you could do (it's very crazy though :).
inspired by Cristian answer I needed to intercept AJAX calls webview is doing, where I needed to intercept response headers to get some information (cart item count in e-commerce app), which I needed to leverage in app. As the app is using okhttp I've ended up doing this and it's working:
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
Log.i(TAG,"shouldInterceptRequest path:"+request.getUrl().getPath());
WebResourceResponse returnResponse = null;
if (request.getUrl().getPath().startsWith("/cart")) { // only interested in /cart requests
returnResponse = super.shouldInterceptRequest(view, request);
Log.i(TAG,"cart AJAX call - doing okRequest");
Request okRequest = new Request.Builder()
.url(request.getUrl().toString())
.post(null)
.build();
try {
Response okResponse = app.getOkHttpClient().newCall(okRequest).execute();
if (okResponse!=null) {
int statusCode = okResponse.code();
String encoding = "UTF-8";
String mimeType = "application/json";
String reasonPhrase = "OK";
Map<String,String> responseHeaders = new HashMap<String,String>();
if (okResponse.headers()!=null) {
if (okResponse.headers().size()>0) {
for (int i = 0; i < okResponse.headers().size(); i++) {
String key = okResponse.headers().name(i);
String value = okResponse.headers().value(i);
responseHeaders.put(key, value);
if (key.toLowerCase().contains("x-cart-itemcount")) {
Log.i(TAG,"setting cart item count");
app.setCartItemsCount(Integer.parseInt(value));
}
}
}
}
InputStream data = new ByteArrayInputStream(okResponse.body().string().getBytes(StandardCharsets.UTF_8));
Log.i(TAG, "okResponse code:" + okResponse.code());
returnResponse = new WebResourceResponse(mimeType,encoding,statusCode,reasonPhrase,responseHeaders,data);
} else {
Log.w(TAG,"okResponse fail");
}
} catch (IOException e) {
e.printStackTrace();
}
}
return returnResponse;
}
I hope this may be helpful to others and if somebody has a suggestions for improvement I would be grateful. Unfortunately it's compatible only with LOLLIPOP and higher as from this version you can access/return headers using WebResourceRequest, which was needed for my case.
You should be able to control all your headers by skipping loadUrl and writing your own loadPage using Java's HttpURLConnection. Then view the headers, do your thing, and use the webview's loadData to display the response.
There is an alternative solution if you're targeting at least Kit-Kat, even though this wouldn't show the headers in the Activity but rather in Chrome. You can simply follow this short guide on how to remotely debug Webviews.
The 2 key points are, first, to enable WebView debugging in you app
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
And then open chrome://inspect in a Chrome tab in a computer. Once you connect your phone via USB to the computer, you will see your app's WebView in the list of debuggable devices
you can use OkHttp:
private fun handleRequestViaOkHttp(url: String) {
var httpClient = OkHttpClient()
thread {
try {
val request = Request.Builder().url(url).build()
print("Request: $request")
val response = httpClient.newCall(request).execute()
println("Response: " + response.headers().toString())
} catch (e: Exception) {}
}
}
you should call it inside this method:
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
handleRequestViaOkHttp(webViewUrl.value.toString())
return super.shouldInterceptRequest(view, request)
}
As the accepted answer will only work with HttpGet, here is a trick thet currently I'm using (at this time it seems to work)
In onPageFinished handler, if there is an error, the title of the page will be like "ERROR_NUM - ERROR_DESCRIPTION", like "500 - Internal Server Error", so all I do is to get title from webview in the function, and then check the title.
view.getTitle()

Categories

Resources