123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- /*
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
- */
- package org.apache.cordova.engine;
-
- import android.annotation.TargetApi;
- import android.content.pm.ApplicationInfo;
- import android.content.pm.PackageManager;
- import android.content.pm.PackageManager.NameNotFoundException;
- import android.graphics.Bitmap;
- import android.net.Uri;
- import android.net.http.SslError;
- import android.os.Build;
- import android.webkit.ClientCertRequest;
- import android.webkit.HttpAuthHandler;
- import android.webkit.SslErrorHandler;
- import android.webkit.WebResourceResponse;
- import android.webkit.WebView;
- import android.webkit.WebViewClient;
-
- import org.apache.cordova.AuthenticationToken;
- import org.apache.cordova.CordovaClientCertRequest;
- import org.apache.cordova.CordovaHttpAuthHandler;
- import org.apache.cordova.CordovaResourceApi;
- import org.apache.cordova.LOG;
- import org.apache.cordova.PluginManager;
-
- import java.io.FileNotFoundException;
- import java.io.IOException;
- import java.util.Hashtable;
-
-
- /**
- * This class is the WebViewClient that implements callbacks for our web view.
- * The kind of callbacks that happen here are regarding the rendering of the
- * document instead of the chrome surrounding it, such as onPageStarted(),
- * shouldOverrideUrlLoading(), etc. Related to but different than
- * CordovaChromeClient.
- */
- public class SystemWebViewClient extends WebViewClient {
-
- private static final String TAG = "SystemWebViewClient";
- protected final SystemWebViewEngine parentEngine;
- private boolean doClearHistory = false;
- boolean isCurrentlyLoading;
-
- /** The authorization tokens. */
- private Hashtable<String, AuthenticationToken> authenticationTokens = new Hashtable<String, AuthenticationToken>();
-
- public SystemWebViewClient(SystemWebViewEngine parentEngine) {
- this.parentEngine = parentEngine;
- }
-
- /**
- * Give the host application a chance to take over the control when a new url
- * is about to be loaded in the current WebView.
- *
- * @param view The WebView that is initiating the callback.
- * @param url The url to be loaded.
- * @return true to override, false for default behavior
- */
- @Override
- @SuppressWarnings("deprecation")
- public boolean shouldOverrideUrlLoading(WebView view, String url) {
- return parentEngine.client.onNavigationAttempt(url);
- }
-
- /**
- * On received http auth request.
- * The method reacts on all registered authentication tokens. There is one and only one authentication token for any host + realm combination
- */
- @Override
- public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
-
- // Get the authentication token (if specified)
- AuthenticationToken token = this.getAuthenticationToken(host, realm);
- if (token != null) {
- handler.proceed(token.getUserName(), token.getPassword());
- return;
- }
-
- // Check if there is some plugin which can resolve this auth challenge
- PluginManager pluginManager = this.parentEngine.pluginManager;
- if (pluginManager != null && pluginManager.onReceivedHttpAuthRequest(null, new CordovaHttpAuthHandler(handler), host, realm)) {
- parentEngine.client.clearLoadTimeoutTimer();
- return;
- }
-
- // By default handle 401 like we'd normally do!
- super.onReceivedHttpAuthRequest(view, handler, host, realm);
- }
-
- /**
- * On received client cert request.
- * The method forwards the request to any running plugins before using the default implementation.
- *
- * @param view
- * @param request
- */
- @Override
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- public void onReceivedClientCertRequest (WebView view, ClientCertRequest request)
- {
-
- // Check if there is some plugin which can resolve this certificate request
- PluginManager pluginManager = this.parentEngine.pluginManager;
- if (pluginManager != null && pluginManager.onReceivedClientCertRequest(null, new CordovaClientCertRequest(request))) {
- parentEngine.client.clearLoadTimeoutTimer();
- return;
- }
-
- // By default pass to WebViewClient
- super.onReceivedClientCertRequest(view, request);
- }
-
- /**
- * Notify the host application that a page has started loading.
- * This method is called once for each main frame load so a page with iframes or framesets will call onPageStarted
- * one time for the main frame. This also means that onPageStarted will not be called when the contents of an
- * embedded frame changes, i.e. clicking a link whose target is an iframe.
- *
- * @param view The webview initiating the callback.
- * @param url The url of the page.
- */
- @Override
- public void onPageStarted(WebView view, String url, Bitmap favicon) {
- super.onPageStarted(view, url, favicon);
- isCurrentlyLoading = true;
- // Flush stale messages & reset plugins.
- parentEngine.bridge.reset();
- parentEngine.client.onPageStarted(url);
- }
-
- /**
- * Notify the host application that a page has finished loading.
- * This method is called only for main frame. When onPageFinished() is called, the rendering picture may not be updated yet.
- *
- *
- * @param view The webview initiating the callback.
- * @param url The url of the page.
- */
- @Override
- public void onPageFinished(WebView view, String url) {
- super.onPageFinished(view, url);
- // Ignore excessive calls, if url is not about:blank (CB-8317).
- if (!isCurrentlyLoading && !url.startsWith("about:")) {
- return;
- }
- isCurrentlyLoading = false;
-
- /**
- * Because of a timing issue we need to clear this history in onPageFinished as well as
- * onPageStarted. However we only want to do this if the doClearHistory boolean is set to
- * true. You see when you load a url with a # in it which is common in jQuery applications
- * onPageStared is not called. Clearing the history at that point would break jQuery apps.
- */
- if (this.doClearHistory) {
- view.clearHistory();
- this.doClearHistory = false;
- }
- parentEngine.client.onPageFinishedLoading(url);
-
- }
-
- /**
- * Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
- * The errorCode parameter corresponds to one of the ERROR_* constants.
- *
- * @param view The WebView that is initiating the callback.
- * @param errorCode The error code corresponding to an ERROR_* value.
- * @param description A String describing the error.
- * @param failingUrl The url that failed to load.
- */
- @Override
- @SuppressWarnings("deprecation")
- public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
- // Ignore error due to stopLoading().
- if (!isCurrentlyLoading) {
- return;
- }
- LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
-
- // If this is a "Protocol Not Supported" error, then revert to the previous
- // page. If there was no previous page, then punt. The application's config
- // is likely incorrect (start page set to sms: or something like that)
- if (errorCode == WebViewClient.ERROR_UNSUPPORTED_SCHEME) {
- parentEngine.client.clearLoadTimeoutTimer();
-
- if (view.canGoBack()) {
- view.goBack();
- return;
- } else {
- super.onReceivedError(view, errorCode, description, failingUrl);
- }
- }
- parentEngine.client.onReceivedError(errorCode, description, failingUrl);
- }
-
- /**
- * Notify the host application that an SSL error occurred while loading a resource.
- * The host application must call either handler.cancel() or handler.proceed().
- * Note that the decision may be retained for use in response to future SSL errors.
- * The default behavior is to cancel the load.
- *
- * @param view The WebView that is initiating the callback.
- * @param handler An SslErrorHandler object that will handle the user's response.
- * @param error The SSL error object.
- */
- @Override
- public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
-
- final String packageName = parentEngine.cordova.getActivity().getPackageName();
- final PackageManager pm = parentEngine.cordova.getActivity().getPackageManager();
-
- ApplicationInfo appInfo;
- try {
- appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
- if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
- // debug = true
- handler.proceed();
- return;
- } else {
- // debug = false
- super.onReceivedSslError(view, handler, error);
- }
- } catch (NameNotFoundException e) {
- // When it doubt, lock it out!
- super.onReceivedSslError(view, handler, error);
- }
- }
-
-
- /**
- * Sets the authentication token.
- *
- * @param authenticationToken
- * @param host
- * @param realm
- */
- public void setAuthenticationToken(AuthenticationToken authenticationToken, String host, String realm) {
- if (host == null) {
- host = "";
- }
- if (realm == null) {
- realm = "";
- }
- this.authenticationTokens.put(host.concat(realm), authenticationToken);
- }
-
- /**
- * Removes the authentication token.
- *
- * @param host
- * @param realm
- *
- * @return the authentication token or null if did not exist
- */
- public AuthenticationToken removeAuthenticationToken(String host, String realm) {
- return this.authenticationTokens.remove(host.concat(realm));
- }
-
- /**
- * Gets the authentication token.
- *
- * In order it tries:
- * 1- host + realm
- * 2- host
- * 3- realm
- * 4- no host, no realm
- *
- * @param host
- * @param realm
- *
- * @return the authentication token
- */
- public AuthenticationToken getAuthenticationToken(String host, String realm) {
- AuthenticationToken token = null;
- token = this.authenticationTokens.get(host.concat(realm));
-
- if (token == null) {
- // try with just the host
- token = this.authenticationTokens.get(host);
-
- // Try the realm
- if (token == null) {
- token = this.authenticationTokens.get(realm);
- }
-
- // if no host found, just query for default
- if (token == null) {
- token = this.authenticationTokens.get("");
- }
- }
-
- return token;
- }
-
- /**
- * Clear all authentication tokens.
- */
- public void clearAuthenticationTokens() {
- this.authenticationTokens.clear();
- }
-
- @Override
- @SuppressWarnings("deprecation")
- public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
- try {
- // Check the against the whitelist and lock out access to the WebView directory
- // Changing this will cause problems for your application
- if (!parentEngine.pluginManager.shouldAllowRequest(url)) {
- LOG.w(TAG, "URL blocked by whitelist: " + url);
- // Results in a 404.
- return new WebResourceResponse("text/plain", "UTF-8", null);
- }
-
- CordovaResourceApi resourceApi = parentEngine.resourceApi;
- Uri origUri = Uri.parse(url);
- // Allow plugins to intercept WebView requests.
- Uri remappedUri = resourceApi.remapUri(origUri);
-
- if (!origUri.equals(remappedUri) || needsSpecialsInAssetUrlFix(origUri) || needsKitKatContentUrlFix(origUri)) {
- CordovaResourceApi.OpenForReadResult result = resourceApi.openForRead(remappedUri, true);
- return new WebResourceResponse(result.mimeType, "UTF-8", result.inputStream);
- }
- // If we don't need to special-case the request, let the browser load it.
- return null;
- } catch (IOException e) {
- if (!(e instanceof FileNotFoundException)) {
- LOG.e(TAG, "Error occurred while loading a file (returning a 404).", e);
- }
- // Results in a 404.
- return new WebResourceResponse("text/plain", "UTF-8", null);
- }
- }
-
- private static boolean needsKitKatContentUrlFix(Uri uri) {
- return "content".equals(uri.getScheme());
- }
-
- private static boolean needsSpecialsInAssetUrlFix(Uri uri) {
- if (CordovaResourceApi.getUriType(uri) != CordovaResourceApi.URI_TYPE_ASSET) {
- return false;
- }
- if (uri.getQuery() != null || uri.getFragment() != null) {
- return true;
- }
-
- if (!uri.toString().contains("%")) {
- return false;
- }
-
- return false;
- }
- }
|