Fab_34
Fab_34

Reputation: 421

Intercept POST requests in a WebView

I'm developping an Android application filtering the requests (with a white list) and using a custom SSLSocketFactory. For this, I've developed a custom WebViewClient and I have overridden the shouldInterceptRequest method. I can filter and use my SocketFactory with the GET requests but I can't intercept the POST requests.

So, is there a way to intercept the POST requests in a WebView ?

Here is the code of the shouldInterceptRequest method :

public final WebResourceResponse shouldInterceptRequest(WebView view, String urlStr) {
    URI uri = URI.create(urlStr);
    String scheme = uri.getScheme();
    // If scheme not http(s), let the default webview manage it
    if(!"http".equals(scheme) && !"https".equals(scheme)) {
        return null;
    }
    URL url = uri.toURL();

    if(doCancelRequest(url)) {
        // Empty response
        Log.d(TAG, "URL filtered: " + url);
        return new WebResourceResponse("text/plain", "UTF-8", new EmptyInputStream());

    } else {
        Log.d(TAG, "URL: " + url);

        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestProperty("User-Agent", mSettings.getUserAgentString());

        // Configure connections
        configureConnection(conn);

        String mimeType = conn.getContentType();
        String encoding = conn.getContentEncoding();

        if(mimeType != null && mimeType.contains(CONTENT_TYPE_SPLIT)) {
            String[] split = mimeType.split(CONTENT_TYPE_SPLIT);
            mimeType = split[0];

            Matcher matcher = CONTENT_TYPE_PATTERN.matcher(split[1]);
            if(matcher.find()) {
                encoding = matcher.group(1);
            }
        }

        InputStream is = conn.getInputStream();
        return new WebResourceResponse(mimeType, encoding, is);
    }
}

Upvotes: 42

Views: 45440

Answers (7)

user3738870
user3738870

Reputation: 1632

I have created a library that aims to capture all data of all HTTP requests sent from Android WebViews.

Using this library, you can easily implement sending POST, or any other requests. Here's your code adapted to work with the library:

    @Nullable
    @Override
    public final WebResourceResponse shouldInterceptRequest(WebView view, WebViewRequest request) {
        URI uri = URI.create(request.getUrl());
        String scheme = uri.getScheme();
        // If scheme not http(s), let the default webview manage it
        if(!"http".equals(scheme) && !"https".equals(scheme)) {
            return null;
        }
        URL url = uri.toURL();

        if(doCancelRequest(url)) {
            // Empty response
            Log.d(TAG, "URL filtered: " + url);
            return new WebResourceResponse("text/plain", "UTF-8", new EmptyInputStream());

        } else {
            Log.d(TAG, "URL: " + url);

            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            // Set request method
            conn.setRequestMethod(request.getMethod());
            // Set request headers
            for (Map.Entry<String, String> header : request.getHeaders().entrySet()) {
                conn.setRequestProperty(header.getKey(), header.getValue());
            }

            // Set request body, if it's present
            if (!request.getBody().isEmpty()) {
                OutputStream os = conn.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8");
                osw.write(request.getBody());
                osw.flush();
                osw.close();
                os.close();  //don't forget to close the OutputStream
            }
            conn.connect();

            String mimeType = conn.getContentType();
            String encoding = conn.getContentEncoding();

            if(mimeType != null && mimeType.contains(CONTENT_TYPE_SPLIT)) {
                String[] split = mimeType.split(CONTENT_TYPE_SPLIT);
                mimeType = split[0];

                Matcher matcher = CONTENT_TYPE_PATTERN.matcher(split[1]);
                if(matcher.find()) {
                    encoding = matcher.group(1);
                }
            }

            InputStream is = conn.getInputStream();
            return new WebResourceResponse(mimeType, encoding, is);
        }
    }

Upvotes: 5

Terrion
Terrion

Reputation: 1

There's a simpler solution: send the parameters by GET in the ajax call and transform them to POST in shouldInterceptRequest

Upvotes: -1

Basic.Bear
Basic.Bear

Reputation: 151

The easiest way I found to do this is to just use the JQuery Ajax Event Handlers with a Javascript Interface.

You have to setup the Javascript Interface, but once you do just have it match the signature of the handler you're interested in.

To get the payload, just find the payload variable name put it in the interface.

Here's the Javascript:

 $( document ).ajaxSend(function( event, request, settings ) {
      var eventJSON= JSON.stringify(event);
      var requestJSON = JSON.stringify(request);
      var settingsJSON = JSON.stringify(settings);
      var payloadJSON = JSON.stringify(payload);

      myJSInterface.passData(eventJSON,requestJSON,settingsJSON,payloadJSON);       
 });

Here's the Kotlin class

 class yourJSInterface() {
      lateinit var event: String
      lateinit var request: String
      lateinit var settings: String
      lateinit var payload: String
 
      @JavascriptInterface
      fun passData(eventJSON:String, responseJSON:String, settingsJSON:String,payloadJSON:String){
           event = eventJSON
           response= responseJSON
           settings= settingsJSON
           payload= payloadJSON
      }
 }

The registration in the onPageStarted override for WebViewClient

 webView.addJavascriptInterface(yourJSInterface,"myJSInterface")

Finally the JS injection into the WebView in the OnPageFinished override

 webView.evaluateJavascript("javascript:" + getString(R.string.js_from_above),null)

I registered the interface in onPageStarted because otherwise, the javascript file in onPageFinished won't recognize your interface.

Upvotes: 0

Konstantin Schubert
Konstantin Schubert

Reputation: 3356

I was facing the same issue a few days ago.

So I built a library that solves it:

https://github.com/KonstantinSchubert/request_data_webviewclient

It is a WebViewClient with a custom WebResourceRequest that contains the POST/PUT/... payload of XMLHttpRequest requests.

It only works for these though - not for forms and other kind of request sources.

The hack works, basically, by injecting a script into the HTML that intercepts XMLHttpRequest calls. It records the post/put/... content and sends it to an android.webkit.JavascriptInterface. There, the request is stashed until the shouldInterceptRequest method is called by Android ...

Upvotes: 9

HenryChuang
HenryChuang

Reputation: 1459

you can get input value before submit
https://github.com/henrychuangtw/WebView-Javascript-Inject

Step 1 : create a class which called by javascript

class MyJavaScriptInterface
{
    @JavascriptInterface
    public void processHTML(String html)
    {
        //called by javascript
    }
}


Step 2 : register interface for javascript

webview1.getSettings().setJavaScriptEnabled(true);
webview1.addJavascriptInterface(new MyJavaScriptInterface(), "MYOBJECT");


Step 3 : inject javascript to page

webview1.setWebViewClient(new WebViewClient() {
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);

        StringBuilder sb = new StringBuilder();
        sb.append("document.getElementsByTagName('form')[0].onsubmit = function () {");
        sb.append("var objPWD, objAccount;var str = '';");
        sb.append("var inputs = document.getElementsByTagName('input');");
        sb.append("for (var i = 0; i < inputs.length; i++) {");
        sb.append("if (inputs[i].type.toLowerCase() === 'password') {objPWD = inputs[i];}");
        sb.append("else if (inputs[i].name.toLowerCase() === 'email') {objAccount = inputs[i];}");
        sb.append("}");
        sb.append("if (objAccount != null) {str += objAccount.value;}");
        sb.append("if (objPWD != null) { str += ' , ' + objPWD.value;}");
        sb.append("window.MYOBJECT.processHTML(str);");
        sb.append("return true;");
        sb.append("};");

        view.loadUrl("javascript:" + sb.toString());
    }

});

Upvotes: -1

chetan pawar
chetan pawar

Reputation: 485

I have one of my answers on above thread http://code.google.com/p/android/issues/detail?id=9122

Please see comment#31

Some of the caveats of my solution I see are:

  1. Putting a dependency on xmlhttprequest prototype which has different implementation for different webkits.
  2. Security issue in sending data for post requests in URL. But I guess you can solve that through some encryption mechanism.
  3. URL length issue for some of the browsers if you big data to post

Apart from that, I found this github repo which seems to be solving this problem in another hacky way. I looked into the code but didn't get time to implement and test it. But worth giving a try.

Upvotes: 0

Raanan
Raanan

Reputation: 4785

Use GET instead of POST.

Known issue: http://code.google.com/p/android/issues/detail?id=9122

Was answered here as well: Android - how to intercept a form POST in android WebViewClient on API level 4

Upvotes: -4

Related Questions