Implement Android WebView BlockNetworkImages
This uses the WebCore ImagesEnabled setting, which is added to
ContentSettings. The setting also includes overriding
WebPermissionClient::allowImage, which here allows images with local
soucres to load as well.
Use a whitelist of local file schemes instead of blacklisting only
http(s).
BUG=
Review URL: https://chromiumcodereview.appspot.com/10920033
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@158809 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwSettingsTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwSettingsTest.java
index f57cffd..70b14bd 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwSettingsTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwSettingsTest.java
@@ -7,6 +7,7 @@
import android.content.Context;
import android.os.Build;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.TestFileUtil;
@@ -14,10 +15,15 @@
import org.chromium.content.browser.ContentSettings;
import org.chromium.content.browser.ContentViewCore;
import org.chromium.content.browser.test.util.CallbackHelper;
+import org.chromium.content.browser.test.util.Criteria;
+import org.chromium.content.browser.test.util.CriteriaHelper;
import org.chromium.content.browser.test.util.HistoryUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* A test suite for ContentSettings class. The key objective is to verify that each
@@ -25,6 +31,8 @@
* application.
*/
public class AwSettingsTest extends AndroidWebViewTestBase {
+ private static final int CHECK_INTERVAL = 100;
+
private static final boolean ENABLED = true;
private static final boolean DISABLED = false;
@@ -1334,4 +1342,84 @@
private String createContentUrl(final String target) {
return TestContentProvider.createContentUrl(target);
}
+
+ private final String IMAGE_DATA = "iVBORw0KGgoAAA" +
+ "ANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAAAXNSR0IArs4c6QAAAA1JREFUCB0BAgD9/wAAAAIAAc3j" +
+ "0SsAAAAASUVORK5CYII=";
+
+ private final String DATA_URL_IMAGE_HTML = "<html>" +
+ "<head><script>function updateTitle(){" +
+ "document.title=document.getElementById('img').naturalHeight;}</script></head>" +
+ "<body onload='updateTitle()'>" +
+ "<img id='img' onload='updateTitle()' src='data:image/png;base64," + IMAGE_DATA +
+ "'></body></html>";
+
+ @SmallTest
+ @Feature({"Android-WebView", "Preferences"})
+ public void testBlockNetworkImagesDoesNotBlockDataUrlImage() throws Throwable {
+ final TestAwContentsClient contentClient = new TestAwContentsClient();
+ final ContentViewCore contentView =
+ createAwTestContainerViewOnMainSync(false, contentClient).getContentViewCore();
+ final ContentSettings settings = getContentSettingsOnUiThread(contentView);
+
+ settings.setJavaScriptEnabled(true);
+
+ settings.setImagesEnabled(false);
+ loadDataSync(contentView,
+ contentClient.getOnPageFinishedHelper(),
+ DATA_URL_IMAGE_HTML,
+ "text/html",
+ false);
+ assertEquals("1", getTitleOnUiThread(contentView));
+ }
+
+ @SmallTest
+ @Feature({"Android-WebView", "Preferences"})
+ public void testBlockNetworkImagesBlocksNetworkImageAndReloadInPlace() throws Throwable {
+ final TestAwContentsClient contentClient = new TestAwContentsClient();
+ final ContentViewCore contentView =
+ createAwTestContainerViewOnMainSync(false, contentClient).getContentViewCore();
+ final ContentSettings settings = getContentSettingsOnUiThread(contentView);
+ settings.setJavaScriptEnabled(true);
+
+ TestWebServer webServer = null;
+ try {
+ webServer = new TestWebServer(false);
+ List<Pair<String, String>> imageHeaders = new ArrayList<Pair<String, String>>();
+ imageHeaders.add(Pair.create("Content-Type", "image/png"));
+ final String imagePath = "/image.png";
+ webServer.setResponseBase64(imagePath, IMAGE_DATA, imageHeaders);
+
+ final String pagePath = "/html_image.html";
+ final String httpUrlImageHtml = "<html>" +
+ "<head><script>" +
+ "function updateTitle(){" +
+ "document.title=document.getElementById('img').naturalHeight;}" +
+ "</script></head>" +
+ "<body onload='updateTitle()'>" +
+ "<img id='img' onload='updateTitle()' src='" + imagePath +
+ "'></body></html>";
+ final String httpImageUrl = webServer.setResponse(pagePath, httpUrlImageHtml, null);
+
+ settings.setImagesEnabled(false);
+ loadUrlSync(contentView, contentClient.getOnPageFinishedHelper(), httpImageUrl);
+ assertEquals("0", getTitleOnUiThread(contentView));
+
+ settings.setImagesEnabled(true);
+ assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
+ @Override
+ public boolean isSatisfied() {
+ try {
+ return "0".equals(getTitleOnUiThread(contentView));
+ } catch (Throwable t) {
+ t.printStackTrace();
+ fail("Failed to getTitleOnUIThread: " + t.toString());
+ return false;
+ }
+ }
+ }, WAIT_TIMEOUT_SECONDS * 1000, CHECK_INTERVAL));
+ } finally {
+ if (webServer != null) webServer.shutdown();
+ }
+ }
}
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/TestWebServer.java b/android_webview/javatests/src/org/chromium/android_webview/test/TestWebServer.java
index 8508a51..0e56f56 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/TestWebServer.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/TestWebServer.java
@@ -15,7 +15,7 @@
import org.apache.http.HttpVersion;
import org.apache.http.RequestLine;
import org.apache.http.StatusLine;
-import org.apache.http.entity.StringEntity;
+import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.impl.cookie.DateUtils;
import org.apache.http.message.BasicHttpResponse;
@@ -26,7 +26,6 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
@@ -72,11 +71,11 @@
private boolean mSsl;
private static class Response {
- final String mResponseStr;
+ final byte[] mResponseData;
final List<Pair<String, String>> mResponseHeaders;
- Response(String responseStr, List<Pair<String, String>> responseHeaders) {
- mResponseStr = responseStr;
+ Response(byte[] resposneData, List<Pair<String, String>> responseHeaders) {
+ mResponseData = resposneData;
mResponseHeaders = responseHeaders == null ?
new ArrayList<Pair<String, String>>() : responseHeaders;
}
@@ -146,16 +145,37 @@
* in (with the option to specify additional headers).
*
* @param requestPath The path to respond to.
- * @param resposneString The response body that will be returned.
+ * @param responseString The response body that will be returned.
* @param responseHeaders Any additional headers that should be returned along with the
* response (null is acceptable).
* @return The full URL including the path that should be requested to get the expected
* response.
*/
public String setResponse(
- String requestPath, String resposneString,
+ String requestPath, String responseString,
List<Pair<String, String>> responseHeaders) {
- mResponseMap.put(requestPath, new Response(resposneString, responseHeaders));
+ mResponseMap.put(requestPath, new Response(responseString.getBytes(), responseHeaders));
+ return mServerUri + requestPath;
+ }
+
+ /**
+ * Sets a base64 encoded response to be returned when a particular request path is passed
+ * in (with the option to specify additional headers).
+ *
+ * @param requestPath The path to respond to.
+ * @param base64EncodedResponse The response body that is base64 encoded. The actual server
+ * response will the decoded binary form.
+ * @param responseHeaders Any additional headers that should be returned along with the
+ * response (null is acceptable).
+ * @return The full URL including the path that should be requested to get the expected
+ * response.
+ */
+ public String setResponseBase64(
+ String requestPath, String base64EncodedResponse,
+ List<Pair<String, String>> responseHeaders) {
+ mResponseMap.put(requestPath,
+ new Response(Base64.decode(base64EncodedResponse, Base64.DEFAULT),
+ responseHeaders));
return mServerUri + requestPath;
}
@@ -234,7 +254,7 @@
httpResponse = createResponse(HttpStatus.SC_NOT_FOUND);
} else {
httpResponse = createResponse(HttpStatus.SC_OK);
- httpResponse.setEntity(createEntity(response.mResponseStr));
+ httpResponse.setEntity(createEntity(response.mResponseData));
for (Pair<String, String> header : response.mResponseHeaders) {
httpResponse.addHeader(header.first, header.second);
}
@@ -272,7 +292,7 @@
buf.append("</title></head><body>");
buf.append(reason);
buf.append("</body></html>");
- response.setEntity(createEntity(buf.toString()));
+ response.setEntity(createEntity(buf.toString().getBytes()));
}
return response;
}
@@ -280,15 +300,10 @@
/**
* Create a string entity for the given content.
*/
- private StringEntity createEntity(String content) {
- try {
- StringEntity entity = new StringEntity(content);
- entity.setContentType("text/html");
- return entity;
- } catch (UnsupportedEncodingException e) {
- Log.w(TAG, e);
- }
- return null;
+ private ByteArrayEntity createEntity(byte[] data) {
+ ByteArrayEntity entity = new ByteArrayEntity(data);
+ entity.setContentType("text/html");
+ return entity;
}
private static class ServerThread extends Thread {
diff --git a/android_webview/renderer/aw_render_view_ext.cc b/android_webview/renderer/aw_render_view_ext.cc
index ae0035d..7f0825c 100644
--- a/android_webview/renderer/aw_render_view_ext.cc
+++ b/android_webview/renderer/aw_render_view_ext.cc
@@ -5,17 +5,20 @@
#include "android_webview/renderer/aw_render_view_ext.h"
#include "android_webview/common/render_view_messages.h"
+#include "content/public/common/url_constants.h"
#include "content/public/renderer/render_view.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURL.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebVector.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebElement.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
-#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebVector.h"
namespace android_webview {
AwRenderViewExt::AwRenderViewExt(content::RenderView* render_view)
: content::RenderViewObserver(render_view) {
+ render_view->GetWebView()->setPermissionClient(this);
}
AwRenderViewExt::~AwRenderViewExt() {}
@@ -48,4 +51,19 @@
hasImages));
}
+bool AwRenderViewExt::allowImage(WebKit::WebFrame* frame,
+ bool enabled_per_settings,
+ const WebKit::WebURL& image_url) {
+ // Implementing setBlockNetworkImages, so allow local scheme images to be
+ // loaded.
+ if (enabled_per_settings)
+ return true;
+
+ // For compatibility, only blacklist network schemes instead of whitelisting.
+ const GURL url(image_url);
+ return !(url.SchemeIs(chrome::kHttpScheme) ||
+ url.SchemeIs(chrome::kHttpsScheme) ||
+ url.SchemeIs(chrome::kFtpScheme));
+}
+
} // namespace android_webview
diff --git a/android_webview/renderer/aw_render_view_ext.h b/android_webview/renderer/aw_render_view_ext.h
index fa58872..fadb457 100644
--- a/android_webview/renderer/aw_render_view_ext.h
+++ b/android_webview/renderer/aw_render_view_ext.h
@@ -8,13 +8,21 @@
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "content/public/renderer/render_view_observer.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebPermissionClient.h"
+
+namespace WebKit {
+
+class WebURL;
+
+} // namespace WebKit
namespace android_webview {
// Render process side of AwRenderViewHostExt, this provides cross-process
// implementation of miscellaneous WebView functions that we need to poke
// WebKit directly to implement (and that aren't needed in the chrome app).
-class AwRenderViewExt : public content::RenderViewObserver {
+class AwRenderViewExt : public content::RenderViewObserver,
+ public WebKit::WebPermissionClient {
public:
static void RenderViewCreated(content::RenderView* render_view);
@@ -27,6 +35,11 @@
void OnDocumentHasImagesRequest(int id);
+ // WebKit::WebPermissionClient implementation.
+ virtual bool allowImage(WebKit::WebFrame* frame,
+ bool enabledPerSettings,
+ const WebKit::WebURL& imageURL);
+
DISALLOW_COPY_AND_ASSIGN(AwRenderViewExt);
};
diff --git a/content/browser/android/content_settings.cc b/content/browser/android/content_settings.cc
index a436c38..e8632d6 100644
--- a/content/browser/android/content_settings.cc
+++ b/content/browser/android/content_settings.cc
@@ -65,6 +65,8 @@
GetFieldID(env, clazz, "mDefaultFixedFontSize", "I");
load_images_automatically =
GetFieldID(env, clazz, "mLoadsImagesAutomatically", "Z");
+ images_enabled =
+ GetFieldID(env, clazz, "mImagesEnabled", "Z");
java_script_enabled =
GetFieldID(env, clazz, "mJavaScriptEnabled", "Z");
allow_universal_access_from_file_urls =
@@ -95,6 +97,7 @@
jfieldID default_font_size;
jfieldID default_fixed_font_size;
jfieldID load_images_automatically;
+ jfieldID images_enabled;
jfieldID java_script_enabled;
jfieldID allow_universal_access_from_file_urls;
jfieldID allow_file_access_from_file_urls;
@@ -197,6 +200,11 @@
CheckException(env);
env->SetBooleanField(
+ obj,
+ field_ids_->images_enabled, prefs.images_enabled);
+ CheckException(env);
+
+ env->SetBooleanField(
obj, field_ids_->java_script_enabled, prefs.javascript_enabled);
CheckException(env);
@@ -294,6 +302,9 @@
prefs.loads_images_automatically =
env->GetBooleanField(obj, field_ids_->load_images_automatically);
+ prefs.images_enabled =
+ env->GetBooleanField(obj, field_ids_->images_enabled);
+
prefs.javascript_enabled =
env->GetBooleanField(obj, field_ids_->java_script_enabled);
diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentSettings.java b/content/public/android/java/src/org/chromium/content/browser/ContentSettings.java
index 7ae55c7..f3b858b 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ContentSettings.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ContentSettings.java
@@ -80,6 +80,7 @@
private int mDefaultFontSize = 16;
private int mDefaultFixedFontSize = 13;
private boolean mLoadsImagesAutomatically = true;
+ private boolean mImagesEnabled = true;
private boolean mJavaScriptEnabled = false;
private boolean mAllowUniversalAccessFromFileURLs = false;
private boolean mAllowFileAccessFromFileURLs = false;
@@ -723,6 +724,8 @@
/**
* Tell the WebView to load image resources automatically.
+ * Note that setting this flag to false this does not block image loads
+ * from WebCore cache.
* @param flag True if the WebView should load images automatically.
*/
public void setLoadsImagesAutomatically(boolean flag) {
@@ -747,6 +750,34 @@
}
/**
+ * Sets whether images are enabled for this WebView. Setting this from
+ * false to true will reload the blocked images in place.
+ * Note that unlike {@link #setLoadsImagesAutomatically}, setting this
+ * flag to false this will block image loads from WebCore cache as well.
+ * The default is true.
+ * @param flag whether the WebView should enable images.
+ */
+ public void setImagesEnabled(boolean flag) {
+ assert mCanModifySettings;
+ synchronized (mContentSettingsLock) {
+ if (mImagesEnabled != flag) {
+ mImagesEnabled = flag;
+ mEventHandler.syncSettingsLocked();
+ }
+ }
+ }
+
+ /**
+ * Gets whether images are enabled for this WebView.
+ * @return true if the WebView has images eanbled
+ */
+ public boolean getImagesEnabled() {
+ synchronized (mContentSettingsLock) {
+ return mImagesEnabled;
+ }
+ }
+
+ /**
* Return true if JavaScript is enabled. <b>Note: The default is false.</b>
*
* @return True if JavaScript is enabled.