3

I'd like to be able to stand between the WebView and the Server and observe and modify any data that goes in or out. This needs to happen within the host application.

I want to intercept everything - not only the simple GET requests that represent page loads. I want to be able to see all requests, regardless of the verb, for example a POST request triggered by a HTML form. I want to see redirects.

What are the use cases:

  • Attach additional headers to requests
  • Read headers from responses
  • Certificate pinning for every single request

I've read everything that I could find online. My conclusion is that there's no custom WebView implementation that I can plug into my application and satisfy my needs. The closest I've gotten so far is by using WebViewClient.shouldInterceptRequest. I execute the request by myself in a customized HTTP client (OkHttpClient) and return the response to the WebView.

Some key reasons why this doesn't work: Can't handle requests, executed within the HTML consistently; Even if I manage to handle them I can't get a hold onto the request body when it's a POST; I can't handle redirects. Last point is expanded in another question by me.

I read that shouldInterceptRequest was never intended for full network communication interception. Currently I'm asking if I can plug my HTTP client somewhere else so that I don't disturb the functionality of the WebView.

Here's my code:

WebViewClient:

@Override
public WebResourceResponse shouldInterceptRequest(final WebView view, String url) {
    Log.d(Constants.Tags.WEBVIEW_CLIENT, "WV REQUEST (OLD) " + url);
    return processRequest(url);
}

@Override
@TargetApi(21)
public WebResourceResponse shouldInterceptRequest(final WebView view, WebResourceRequest
        interceptedRequest) {
    Log.d(Constants.Tags.WEBVIEW_CLIENT, "WV REQUEST (NEW) " + interceptedRequest.getUrl
            ().toString() + " " + interceptedRequest.getMethod());
    return processRequest(interceptedRequest);
}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private WebResourceResponse processRequest(WebResourceRequest ir) {
    if (!"GET".equals(ir.getMethod())) {
        Log.d(Constants.Tags.WEBVIEW_CLIENT, "IGNORING " + ir.getMethod() + " " + ir.getUrl());
        return null;
    }
    return processRequest(ir.getUrl().toString());
}

private WebResourceResponse processRequest(String url) {
    Request request = new Request.Builder().url(url).build();
    try {
        return processResponse(okHttpClient.newCall(request).execute());
    } catch (SSLHandshakeException e) {
        Log.d(Constants.Tags.WEBVIEW_CLIENT, "SSLHandshakeException: " + e.getMessage());
    } catch (IOException e) {
        Log.d(Constants.Tags.WEBVIEW_CLIENT, "IOException: " + e.getMessage());
        e.printStackTrace();
    }
    return null;
}

private WebResourceResponse processResponse(Response response) {
    String contentType = response.body().contentType().toString();

    if (contentType != null) {
        String mimeType = contentType;

        if (contentType.contains(";")) {
            mimeType = contentType.split(";")[0].trim();
        }

        WebResourceResponse webResourceResponse = new WebResourceResponse(mimeType, response
                .header("content-encoding", "utf-8")
                , response.body().byteStream());

        if (Build.VERSION.SDK_INT > 21) {
            webResourceResponse.setResponseHeaders(convertHeaders(response.headers()));
            webResourceResponse.setStatusCodeAndReasonPhrase(response.code(),"whatever");
        }

        return webResourceResponse;
    }
    return null;
}

private Map<String, String> convertHeaders(Headers headers) {
    Map<String, String> map = new HashMap<>();
    for (int i = 0; i < headers.size(); i++) {
        map.put(headers.name(i), headers.value(i));
    }
    return map;
}

OkHttpClient:

public static OkHttpClient getNewHttpClient() {

    CertificatePinner.Builder certPinnerBuilder = new CertificatePinner.Builder();

    for (Map.Entry<String, String> entry : THUMB_PRINTS.entrySet()) {
        certPinnerBuilder.add(entry.getKey(), entry.getValue());
    }

    CertificatePinner certPinner = certPinnerBuilder.build();

    OkHttpClient.Builder client = new OkHttpClient.Builder()
            .certificatePinner(certPinner)
            .addNetworkInterceptor(new AdditionalHeaderInterceptor())
            .followRedirects(false)
            .followSslRedirects(false)
            .retryOnConnectionFailure(true)
            .connectTimeout(Constants.REQUEST_TIMEOUT, TimeUnit.SECONDS)
            .readTimeout(Constants.REQUEST_TIMEOUT, TimeUnit.SECONDS)
            .writeTimeout(Constants.REQUEST_TIMEOUT, TimeUnit.SECONDS)
            .cache(null);

    return enableTls12OnPreLollipop(client).build();
}

public static OkHttpClient.Builder enableTls12OnPreLollipop(OkHttpClient.Builder client) {
    if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 22) {
        try {
            SSLContext sc = SSLContext.getInstance("TLSv1.2");
            sc.init(null, null, null);
            client.sslSocketFactory(new Tls12SocketFactory(sc.getSocketFactory()));

            ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
                    .tlsVersions(TlsVersion.TLS_1_2)
                    .build();

            List<ConnectionSpec> specs = new ArrayList<>();
            specs.add(cs);
            specs.add(ConnectionSpec.COMPATIBLE_TLS);
            specs.add(ConnectionSpec.CLEARTEXT);

            client.connectionSpecs(specs);
        } catch (Exception exc) {
            Log.e(Constants.Tags.GENERAL, "Error while setting TLS 1.2", exc);
        }
    }

    return client;
}
Dean Panayotov
  • 332
  • 4
  • 16
  • Why do you post the question 2 times? https://stackoverflow.com/questions/46636535/why-doesnt-webviewclient-shouldinterceptrequest-support-redirects-after-4-4 – Steven Oct 09 '17 at 06:42
  • @Steven: This question is trying to solve my practical problem: intercept all WebView communication, while the other question concentrates on handling redirects within the WebView. Same code snippet, different focus. – Dean Panayotov Oct 09 '17 at 08:28

0 Answers0