Skip to content
Permalink
Browse files
[GLIB] Add API to set WebView's Content-Security-Policy
https://bugs.webkit.org/show_bug.cgi?id=240221

Reviewed by Michael Catanzaro and Adrian Perez de Castro.

This adds API to set the WebView's default policy as well as API to
indicate that the WebView is in a WebExtension. This changes the internal
behavior of CSP to block some options.

Both of these are required for a complete WebExtension implementation in
browsers such as Epiphany.

* Tools/TestWebKitAPI/Tests/WebKitGLib/TestWebKitWebView.cpp:
(testWebViewDefaultContentSecurityPolicy):
(testWebViewWebExtensionMode):
(beforeAll):
* Tools/TestWebKitAPI/glib/WebKitGLib/WebViewTest.cpp:
(WebViewTest::loadHtml):
* Tools/TestWebKitAPI/glib/WebKitGLib/WebViewTest.h:
* Source/WebKit/UIProcess/API/glib/WebKitWebContext.cpp:
(webkitWebContextCreatePageForWebView):
* Source/WebKit/UIProcess/API/glib/WebKitWebView.cpp:
(webkitWebViewSetProperty):
(webkitWebViewGetProperty):
(webkit_web_view_class_init):
(webkit_web_view_get_web_extension_mode):
(webkit_web_view_get_default_content_security_policy):
* Source/WebKit/UIProcess/API/gtk/WebKitWebView.h:
* Source/WebKit/UIProcess/API/wpe/WebKitWebView.h:

Canonical link: https://commits.webkit.org/251684@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@295679 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
TingPing committed Jun 21, 2022
1 parent 03dc6cb commit 4db94c2
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 5 deletions.
@@ -60,6 +60,7 @@
#include "WebsiteDataStore.h"
#include "WebsiteDataType.h"
#include <JavaScriptCore/RemoteInspector.h>
#include <WebCore/ContentSecurityPolicy.h>
#include <WebCore/ResourceLoaderIdentifier.h>
#include <glib/gi18n-lib.h>
#include <libintl.h>
@@ -1943,6 +1944,17 @@ void webkitWebContextCreatePageForWebView(WebKitWebContext* context, WebKitWebVi
pageConfiguration->setUserContentController(userContentManager ? webkitUserContentManagerGetUserContentControllerProxy(userContentManager) : nullptr);
pageConfiguration->setControlledByAutomation(webkit_web_view_is_controlled_by_automation(webView));

WebKitWebExtensionMode webExtensionMode = webkit_web_view_get_web_extension_mode(webView);
const char* defaultContentSecurityPolicy = webkit_web_view_get_default_content_security_policy(webView);

if (webExtensionMode == WEBKIT_WEB_EXTENSION_MODE_MANIFESTV3)
pageConfiguration->setContentSecurityPolicyModeForExtension(WebCore::ContentSecurityPolicyModeForExtension::ManifestV3);
else if (webExtensionMode == WEBKIT_WEB_EXTENSION_MODE_MANIFESTV2)
pageConfiguration->setContentSecurityPolicyModeForExtension(WebCore::ContentSecurityPolicyModeForExtension::ManifestV2);

if (defaultContentSecurityPolicy)
pageConfiguration->setOverrideContentSecurityPolicy(String::fromUTF8(defaultContentSecurityPolicy));

WebKitWebsiteDataManager* manager = webkitWebViewGetWebsiteDataManager(webView);
if (!manager)
manager = context->priv->websiteDataManager.get();
@@ -211,6 +211,9 @@ enum {
PROP_MICROPHONE_CAPTURE_STATE,
PROP_DISPLAY_CAPTURE_STATE,

PROP_WEB_EXTENSION_MODE,
PROP_DEFAULT_CONTENT_SECURITY_POLICY,

N_PROPERTIES,
};

@@ -317,6 +320,9 @@ struct _WebKitWebViewPrivate {
GRefPtr<WebKitWebsiteDataManager> websiteDataManager;
GRefPtr<WebKitWebsitePolicies> websitePolicies;

CString defaultContentSecurityPolicy;
WebKitWebExtensionMode webExtensionMode;

double textScaleFactor;

bool isWebProcessResponsive;
@@ -901,6 +907,12 @@ static void webkitWebViewSetProperty(GObject* object, guint propId, const GValue
case PROP_DISPLAY_CAPTURE_STATE:
webkit_web_view_set_display_capture_state(webView, static_cast<WebKitMediaCaptureState>(g_value_get_enum(value)));
break;
case PROP_WEB_EXTENSION_MODE:
webView->priv->webExtensionMode = static_cast<WebKitWebExtensionMode>(g_value_get_enum(value));
break;
case PROP_DEFAULT_CONTENT_SECURITY_POLICY:
webView->priv->defaultContentSecurityPolicy = CString(g_value_get_string(value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
}
@@ -981,6 +993,12 @@ static void webkitWebViewGetProperty(GObject* object, guint propId, GValue* valu
case PROP_DISPLAY_CAPTURE_STATE:
g_value_set_enum(value, webkit_web_view_get_display_capture_state(webView));
break;
case PROP_WEB_EXTENSION_MODE:
g_value_set_enum(value, webkit_web_view_get_web_extension_mode(webView));
break;
case PROP_DEFAULT_CONTENT_SECURITY_POLICY:
g_value_set_string(value, webkit_web_view_get_default_content_security_policy(webView));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
}
@@ -1450,6 +1468,50 @@ static void webkit_web_view_class_init(WebKitWebViewClass* webViewClass)
WEBKIT_MEDIA_CAPTURE_STATE_NONE,
WEBKIT_PARAM_READWRITE);

/**
* WebKitWebView:web-extension-mode:
*
* This configures @web_view to treat the content as a WebExtension.
*
* Note that this refers to the web standard [WebExtensions](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions)
* and not WebKitWebExtensions.
*
* In practice this limits the Content-Security-Policies that are allowed to be set. Some details can be found in
* [Chrome's documentation](https://developer.chrome.com/docs/extensions/mv3/intro/mv3-migration/#content-security-policy).
*
* Since: 2.38
*/
sObjProperties[PROP_WEB_EXTENSION_MODE] = g_param_spec_enum(
"web-extension-mode",
"WebExtension Mode",
_("Enables WebExtension mode"),
WEBKIT_TYPE_WEB_EXTENSION_MODE,
WEBKIT_WEB_EXTENSION_MODE_NONE,
static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

/**
* WebKitWebView:default-content-security-policy:
*
* The default Content-Security-Policy used by the webview as if it were set
* by an HTTP header.
*
* This applies to all content loaded including through navigation or via the various
* webkit_web_view_load_\* APIs. However do note that many WebKit APIs bypass
* Content-Security-Policy in general such as #WebKitUserContentManager and
* webkit_web_view_run_javascript().
*
* Policies are additive so if a website sets its own policy it still applies
* on top of the policy set here.
*
* Since: 2.38
*/
sObjProperties[PROP_DEFAULT_CONTENT_SECURITY_POLICY] = g_param_spec_string(
"default-content-security-policy",
"Default Content-Security-Policy",
_("The default Content-Security-Policy"),
nullptr,
static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

g_object_class_install_properties(gObjectClass, N_PROPERTIES, sObjProperties);

/**
@@ -5178,3 +5240,41 @@ void webkitSetCachedProcessSuspensionDelayForTesting(double seconds)
{
WebKit::WebsiteDataStore::setCachedProcessSuspensionDelayForTesting(Seconds(seconds));
}

/**
* webkit_web_view_get_web_extension_mode:
* @web_view: a #WebKitWebView
*
* Get the view's #WebKitWebExtensionMode.
*
* Returns: the #WebKitWebExtensionMode
*
* Since: 2.38
*/
WebKitWebExtensionMode webkit_web_view_get_web_extension_mode(WebKitWebView* webView)
{
g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), WEBKIT_WEB_EXTENSION_MODE_NONE);

return webView->priv->webExtensionMode;
}

/**
* webkit_web_view_get_default_content_security_policy:
* @web_view: a #WebKitWebView
*
* Gets the configured default Content-Security-Policy.
*
* Returns: (nullable): The default policy or %NULL
*
* Since: 2.38
*/
const gchar*
webkit_web_view_get_default_content_security_policy(WebKitWebView* webView)
{
g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), nullptr);

if (webView->priv->defaultContentSecurityPolicy.isNull())
return nullptr;

return webView->priv->defaultContentSecurityPolicy.data();
}
@@ -221,6 +221,23 @@ typedef enum {
WEBKIT_MEDIA_CAPTURE_STATE_MUTED,
} WebKitMediaCaptureState;

/**
* WebKitWebExtensionMode:
* @WEBKIT_WEB_EXTENSION_MODE_NONE: Not for an extension.
* @WEBKIT_WEB_EXTENSION_MODE_MANIFESTV2: For a ManifestV2 extension.
* @WEBKIT_WEB_EXTENSION_MODE_MANIFESTV3: For a ManifestV3 extension.
*
* Enum values used for setting if a #WebKitWebView is intended for
* WebExtensions.
*
* Since: 2.38
*/
typedef enum {
WEBKIT_WEB_EXTENSION_MODE_NONE,
WEBKIT_WEB_EXTENSION_MODE_MANIFESTV2,
WEBKIT_WEB_EXTENSION_MODE_MANIFESTV3,
} WebKitWebExtensionMode;

struct _WebKitWebView {
WebKitWebViewBase parent;

@@ -648,6 +665,12 @@ WEBKIT_API void
webkit_web_view_set_display_capture_state (WebKitWebView *web_view,
WebKitMediaCaptureState state);

WEBKIT_API WebKitWebExtensionMode
webkit_web_view_get_web_extension_mode (WebKitWebView *web_view);

WEBKIT_API const gchar*
webkit_web_view_get_default_content_security_policy (WebKitWebView *web_view);

G_END_DECLS

#endif
@@ -202,6 +202,23 @@ typedef enum {
WEBKIT_MEDIA_CAPTURE_STATE_MUTED,
} WebKitMediaCaptureState;

/**
* WebKitWebExtensionMode:
* @WEBKIT_WEB_EXTENSION_MODE_NONE: Not for an extension.
* @WEBKIT_WEB_EXTENSION_MODE_MANIFESTV2: For a ManifestV2 extension.
* @WEBKIT_WEB_EXTENSION_MODE_MANIFESTV3: For a ManifestV3 extension.
*
* Enum values used for setting if a #WebKitWebView is intended for
* WebExtensions.
*
* Since: 2.38
*/
typedef enum {
WEBKIT_WEB_EXTENSION_MODE_NONE,
WEBKIT_WEB_EXTENSION_MODE_MANIFESTV2,
WEBKIT_WEB_EXTENSION_MODE_MANIFESTV3,
} WebKitWebExtensionMode;

struct _WebKitWebView {
GObject parent;

@@ -625,6 +642,12 @@ WEBKIT_API void
webkit_web_view_set_display_capture_state (WebKitWebView *web_view,
WebKitMediaCaptureState state);

WEBKIT_API WebKitWebExtensionMode
webkit_web_view_get_web_extension_mode (WebKitWebView *web_view);

WEBKIT_API const gchar*
webkit_web_view_get_default_content_security_policy (WebKitWebView *web_view);

G_END_DECLS

#endif
@@ -1857,6 +1857,71 @@ static void testWebViewCORSAllowlist(WebViewTest* test, gconstpointer)
g_assert_cmpint(waitForFooChanged(), ==, 200);
}

static void testWebViewDefaultContentSecurityPolicy(WebViewTest* test, gconstpointer)
{
GUniqueOutPtr<GError> error;
WebKitJavascriptResult* javascriptResult;

// Sanity check that eval works normally.
javascriptResult = test->runJavaScriptAndWaitUntilFinished("eval('\"allowed\"')", &error.outPtr());
g_assert_nonnull(javascriptResult);
g_assert_no_error(error.get());
GUniquePtr<char> evalValue(WebViewTest::javascriptResultToCString(javascriptResult));
g_assert_cmpstr(evalValue.get(), ==, "allowed");
webkit_javascript_result_unref(javascriptResult);

// Create a new web view with a policy that blocks eval().
auto webView = Test::adoptView(g_object_new(WEBKIT_TYPE_WEB_VIEW,
"default-content-security-policy", "script-src 'self'", nullptr));

// Ensure JavaScript still functions.
javascriptResult = test->runJavaScriptAndWaitUntilFinished("'allowed'", &error.outPtr(), webView.get());
g_assert_nonnull(javascriptResult);
g_assert_no_error(error.get());
GUniquePtr<char> value(WebViewTest::javascriptResultToCString(javascriptResult));
g_assert_cmpstr(value.get(), ==, "allowed");
webkit_javascript_result_unref(javascriptResult);

// Then ensure eval is blocked.
javascriptResult = test->runJavaScriptAndWaitUntilFinished("eval('\"allowed\"')", &error.outPtr(), webView.get());
g_assert_null(javascriptResult);
g_assert_error(error.get(), WEBKIT_JAVASCRIPT_ERROR, WEBKIT_JAVASCRIPT_ERROR_SCRIPT_FAILED);
}

static void testWebViewWebExtensionMode(WebViewTest* test, gconstpointer)
{
GUniqueOutPtr<GError> error;
WebKitJavascriptResult* javascriptResult;
static const char* html =
"<html>"
" <head>"
" <title>unset</title>"
" <meta http-equiv=\"Content-Security-Policy\" content=\"script-src 'unsafe-inline';\">"
" <script>document.title = 'set';</script>"
" </head>"
"</html>";

// Sanity check that this HTML works as expected.
test->loadHtml(html, nullptr);
test->waitUntilLoadFinished();
javascriptResult = test->runJavaScriptAndWaitUntilFinished("document.title == 'set';", &error.outPtr());
g_assert_nonnull(javascriptResult);
g_assert_no_error(error.get());
g_assert_true(WebViewTest::javascriptResultToBoolean(javascriptResult));
webkit_javascript_result_unref(javascriptResult);

// Create a new web view with an extension mode that blocks the unsafe-inline keyword.
auto webView = Test::adoptView(g_object_new(WEBKIT_TYPE_WEB_VIEW,
"web-extension-mode", WEBKIT_WEB_EXTENSION_MODE_MANIFESTV3,
nullptr));
test->loadHtml(html, nullptr, webView.get());
test->waitUntilLoadFinished(webView.get());
javascriptResult = test->runJavaScriptAndWaitUntilFinished("document.title == 'unset';", &error.outPtr(), webView.get());
g_assert_nonnull(javascriptResult);
g_assert_no_error(error.get());
g_assert_true(WebViewTest::javascriptResultToBoolean(javascriptResult));
}

#if USE(SOUP2)
static void serverCallback(SoupServer* server, SoupMessage* message, const char* path, GHashTable*, SoupClientContext*, gpointer)
#else
@@ -1930,6 +1995,8 @@ void beforeAll()
WebViewTerminateWebProcessTest::add("WebKitWebView", "terminate-web-process", testWebViewTerminateWebProcess);
WebViewTerminateWebProcessTest::add("WebKitWebView", "terminate-unresponsive-web-process", testWebViewTerminateUnresponsiveWebProcess);
WebViewTest::add("WebKitWebView", "cors-allowlist", testWebViewCORSAllowlist);
WebViewTest::add("WebKitWebView", "default-content-security-policy", testWebViewDefaultContentSecurityPolicy);
WebViewTest::add("WebKitWebView", "web-extension-mode", testWebViewWebExtensionMode);
}

void afterAll()
@@ -85,15 +85,19 @@ void WebViewTest::loadURI(const char* uri)
g_assert_cmpstr(webkit_web_view_get_uri(m_webView), ==, m_activeURI.data());
}

void WebViewTest::loadHtml(const char* html, const char* baseURI)
void WebViewTest::loadHtml(const char* html, const char* baseURI, WebKitWebView* webView)
{
if (!baseURI)
m_activeURI = "about:blank";
else
m_activeURI = baseURI;
webkit_web_view_load_html(m_webView, html, baseURI);
g_assert_true(webkit_web_view_is_loading(m_webView));
g_assert_cmpstr(webkit_web_view_get_uri(m_webView), ==, m_activeURI.data());

if (!webView)
webView = m_webView;

webkit_web_view_load_html(webView, html, baseURI);
g_assert_true(webkit_web_view_is_loading(webView));
g_assert_cmpstr(webkit_web_view_get_uri(webView), ==, m_activeURI.data());
}

void WebViewTest::loadPlainText(const char* plainText)
@@ -37,7 +37,7 @@ class WebViewTest: public Test {
void platformDestroy();

virtual void loadURI(const char* uri);
virtual void loadHtml(const char* html, const char* baseURI);
virtual void loadHtml(const char* html, const char* baseURI, WebKitWebView* = nullptr);
virtual void loadPlainText(const char* plainText);
virtual void loadRequest(WebKitURIRequest*);
virtual void loadBytes(GBytes*, const char* mimeType, const char* encoding, const char* baseURI);

0 comments on commit 4db94c2

Please sign in to comment.