/* * Copyright (C) 2012 Igalia S.L. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include "LoadTrackingTest.h" #include "WebKitTestServer.h" #include static WebKitTestServer* kHttpsServer; static WebKitTestServer* kHttpServer; static const char* indexHTML = "Testing WebKit2GTK+ SSL"; static const char* insecureContentHTML = "

Text + image

"; static const char TLSExpectedSuccessTitle[] = "WebKit2Gtk+ TLS permission test"; static const char TLSSuccessHTMLString[] = "WebKit2Gtk+ TLS permission test"; class SSLTest: public LoadTrackingTest { public: MAKE_GLIB_TEST_FIXTURE(SSLTest); SSLTest() : m_tlsErrors(static_cast(0)) { } virtual void provisionalLoadFailed(const gchar* failingURI, GError* error) { g_assert_error(error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED); LoadTrackingTest::provisionalLoadFailed(failingURI, error); } virtual void loadCommitted() { GTlsCertificate* certificate = 0; webkit_web_view_get_tls_info(m_webView, &certificate, &m_tlsErrors); m_certificate = certificate; LoadTrackingTest::loadCommitted(); } void waitUntilLoadFinished() { m_certificate = 0; m_tlsErrors = static_cast(0); LoadTrackingTest::waitUntilLoadFinished(); } GRefPtr m_certificate; GTlsCertificateFlags m_tlsErrors; }; static void testSSL(SSLTest* test, gconstpointer) { WebKitWebContext* context = webkit_web_view_get_context(test->m_webView); WebKitTLSErrorsPolicy originalPolicy = webkit_web_context_get_tls_errors_policy(context); webkit_web_context_set_tls_errors_policy(context, WEBKIT_TLS_ERRORS_POLICY_IGNORE); test->loadURI(kHttpsServer->getURIForPath("/").data()); test->waitUntilLoadFinished(); g_assert(test->m_certificate); // We always expect errors because we are using a self-signed certificate, // but only G_TLS_CERTIFICATE_UNKNOWN_CA flags should be present. g_assert(test->m_tlsErrors); g_assert_cmpuint(test->m_tlsErrors, ==, G_TLS_CERTIFICATE_UNKNOWN_CA); // Non HTTPS loads shouldn't have a certificate nor errors. test->loadHtml(indexHTML, 0); test->waitUntilLoadFinished(); g_assert(!test->m_certificate); g_assert(!test->m_tlsErrors); webkit_web_context_set_tls_errors_policy(context, originalPolicy); } class InsecureContentTest: public WebViewTest { public: MAKE_GLIB_TEST_FIXTURE(InsecureContentTest); InsecureContentTest() : m_insecureContentRun(false) , m_insecureContentDisplayed(false) { g_signal_connect(m_webView, "insecure-content-detected", G_CALLBACK(insecureContentDetectedCallback), this); } static void insecureContentDetectedCallback(WebKitWebView* webView, WebKitInsecureContentEvent event, InsecureContentTest* test) { g_assert(webView == test->m_webView); if (event == WEBKIT_INSECURE_CONTENT_RUN) test->m_insecureContentRun = true; if (event == WEBKIT_INSECURE_CONTENT_DISPLAYED) test->m_insecureContentDisplayed = true; } bool m_insecureContentRun; bool m_insecureContentDisplayed; }; static void testInsecureContent(InsecureContentTest* test, gconstpointer) { WebKitWebContext* context = webkit_web_view_get_context(test->m_webView); WebKitTLSErrorsPolicy originalPolicy = webkit_web_context_get_tls_errors_policy(context); webkit_web_context_set_tls_errors_policy(context, WEBKIT_TLS_ERRORS_POLICY_IGNORE); test->loadURI(kHttpsServer->getURIForPath("/insecure-content/").data()); test->waitUntilLoadFinished(); g_assert(!test->m_insecureContentRun); // Images are currently always displayed, even bypassing mixed content settings. Check // https://bugs.webkit.org/show_bug.cgi?id=142469 g_assert(test->m_insecureContentDisplayed); webkit_web_context_set_tls_errors_policy(context, originalPolicy); } static bool assertIfSSLRequestProcessed = false; static void testTLSErrorsPolicy(SSLTest* test, gconstpointer) { WebKitWebContext* context = webkit_web_view_get_context(test->m_webView); // TLS errors are treated as transport failures by default. g_assert(webkit_web_context_get_tls_errors_policy(context) == WEBKIT_TLS_ERRORS_POLICY_FAIL); assertIfSSLRequestProcessed = true; test->loadURI(kHttpsServer->getURIForPath("/").data()); test->waitUntilLoadFinished(); g_assert(test->m_loadFailed); g_assert(test->m_loadEvents.contains(LoadTrackingTest::ProvisionalLoadFailed)); g_assert(!test->m_loadEvents.contains(LoadTrackingTest::LoadCommitted)); assertIfSSLRequestProcessed = false; webkit_web_context_set_tls_errors_policy(context, WEBKIT_TLS_ERRORS_POLICY_IGNORE); g_assert(webkit_web_context_get_tls_errors_policy(context) == WEBKIT_TLS_ERRORS_POLICY_IGNORE); test->m_loadFailed = false; test->loadURI(kHttpsServer->getURIForPath("/").data()); test->waitUntilLoadFinished(); g_assert(!test->m_loadFailed); webkit_web_context_set_tls_errors_policy(context, WEBKIT_TLS_ERRORS_POLICY_FAIL); g_assert(webkit_web_context_get_tls_errors_policy(context) == WEBKIT_TLS_ERRORS_POLICY_FAIL); } static void testTLSErrorsRedirect(SSLTest* test, gconstpointer) { WebKitWebContext* context = webkit_web_view_get_context(test->m_webView); WebKitTLSErrorsPolicy originalPolicy = webkit_web_context_get_tls_errors_policy(context); webkit_web_context_set_tls_errors_policy(context, WEBKIT_TLS_ERRORS_POLICY_FAIL); assertIfSSLRequestProcessed = true; test->loadURI(kHttpsServer->getURIForPath("/redirect").data()); test->waitUntilLoadFinished(); g_assert(test->m_loadFailed); g_assert(test->m_loadEvents.contains(LoadTrackingTest::ProvisionalLoadFailed)); g_assert(!test->m_loadEvents.contains(LoadTrackingTest::LoadCommitted)); assertIfSSLRequestProcessed = false; webkit_web_context_set_tls_errors_policy(context, originalPolicy); } static gboolean webViewAuthenticationCallback(WebKitWebView*, WebKitAuthenticationRequest* request) { g_assert_not_reached(); return TRUE; } static void testTLSErrorsHTTPAuth(SSLTest* test, gconstpointer) { WebKitWebContext* context = webkit_web_view_get_context(test->m_webView); WebKitTLSErrorsPolicy originalPolicy = webkit_web_context_get_tls_errors_policy(context); webkit_web_context_set_tls_errors_policy(context, WEBKIT_TLS_ERRORS_POLICY_FAIL); assertIfSSLRequestProcessed = true; g_signal_connect(test->m_webView, "authenticate", G_CALLBACK(webViewAuthenticationCallback), NULL); test->loadURI(kHttpsServer->getURIForPath("/auth").data()); test->waitUntilLoadFinished(); g_assert(test->m_loadFailed); g_assert(test->m_loadEvents.contains(LoadTrackingTest::ProvisionalLoadFailed)); g_assert(!test->m_loadEvents.contains(LoadTrackingTest::LoadCommitted)); assertIfSSLRequestProcessed = false; webkit_web_context_set_tls_errors_policy(context, originalPolicy); } class TLSErrorsTest: public SSLTest { public: MAKE_GLIB_TEST_FIXTURE(TLSErrorsTest); TLSErrorsTest() : m_tlsErrors(static_cast(0)) , m_failingURI(nullptr) { } ~TLSErrorsTest() { if (m_failingURI) soup_uri_free(m_failingURI); } bool loadFailedWithTLSErrors(const char* failingURI, GTlsCertificate* certificate, GTlsCertificateFlags tlsErrors) override { LoadTrackingTest::loadFailedWithTLSErrors(failingURI, certificate, tlsErrors); assertObjectIsDeletedWhenTestFinishes(G_OBJECT(certificate)); m_certificate = certificate; m_tlsErrors = tlsErrors; if (m_failingURI) soup_uri_free(m_failingURI); m_failingURI = soup_uri_new(failingURI); return true; } GTlsCertificate* certificate() const { return m_certificate.get(); } GTlsCertificateFlags tlsErrors() const { return m_tlsErrors; } const char* host() const { return m_failingURI->host; } private: GRefPtr m_certificate; GTlsCertificateFlags m_tlsErrors; SoupURI* m_failingURI; }; static void testLoadFailedWithTLSErrors(TLSErrorsTest* test, gconstpointer) { WebKitWebContext* context = webkit_web_view_get_context(test->m_webView); WebKitTLSErrorsPolicy originalPolicy = webkit_web_context_get_tls_errors_policy(context); webkit_web_context_set_tls_errors_policy(context, WEBKIT_TLS_ERRORS_POLICY_FAIL); assertIfSSLRequestProcessed = true; // The load-failed-with-tls-errors signal should be emitted when there is a TLS failure. test->loadURI(kHttpsServer->getURIForPath("/test-tls/").data()); test->waitUntilLoadFinished(); g_assert(G_IS_TLS_CERTIFICATE(test->certificate())); g_assert_cmpuint(test->tlsErrors(), ==, G_TLS_CERTIFICATE_UNKNOWN_CA); g_assert_cmpstr(test->host(), ==, soup_uri_get_host(kHttpsServer->baseURI())); g_assert_cmpint(test->m_loadEvents[0], ==, LoadTrackingTest::ProvisionalLoadStarted); g_assert_cmpint(test->m_loadEvents[1], ==, LoadTrackingTest::LoadFailedWithTLSErrors); g_assert_cmpint(test->m_loadEvents[2], ==, LoadTrackingTest::LoadFinished); assertIfSSLRequestProcessed = false; // Test allowing an exception for this certificate on this host. webkit_web_context_allow_tls_certificate_for_host(context, test->certificate(), test->host()); // The page should now load without errors. test->loadURI(kHttpsServer->getURIForPath("/test-tls/").data()); test->waitUntilLoadFinished(); g_assert_cmpint(test->m_loadEvents[0], ==, LoadTrackingTest::ProvisionalLoadStarted); g_assert_cmpint(test->m_loadEvents[1], ==, LoadTrackingTest::LoadCommitted); g_assert_cmpint(test->m_loadEvents[2], ==, LoadTrackingTest::LoadFinished); g_assert_cmpstr(webkit_web_view_get_title(test->m_webView), ==, TLSExpectedSuccessTitle); webkit_web_context_set_tls_errors_policy(context, originalPolicy); } class TLSSubresourceTest : public WebViewTest { public: MAKE_GLIB_TEST_FIXTURE(TLSSubresourceTest); static void resourceLoadStartedCallback(WebKitWebView* webView, WebKitWebResource* resource, WebKitURIRequest* request, TLSSubresourceTest* test) { if (webkit_web_view_get_main_resource(test->m_webView) == resource) return; // Ignore favicons. if (g_str_has_suffix(webkit_uri_request_get_uri(request), "favicon.ico")) return; test->subresourceLoadStarted(resource); } TLSSubresourceTest() : m_tlsErrors(static_cast(0)) { g_signal_connect(m_webView, "resource-load-started", G_CALLBACK(resourceLoadStartedCallback), this); } static void subresourceFailedCallback(WebKitWebResource*, GError*) { g_assert_not_reached(); } static void subresourceFailedWithTLSErrorsCallback(WebKitWebResource* resource, GTlsCertificate* certificate, GTlsCertificateFlags tlsErrors, TLSSubresourceTest* test) { test->subresourceFailedWithTLSErrors(resource, certificate, tlsErrors); } void subresourceLoadStarted(WebKitWebResource* resource) { g_signal_connect(resource, "failed", G_CALLBACK(subresourceFailedCallback), nullptr); g_signal_connect(resource, "failed-with-tls-errors", G_CALLBACK(subresourceFailedWithTLSErrorsCallback), this); } void subresourceFailedWithTLSErrors(WebKitWebResource* resource, GTlsCertificate* certificate, GTlsCertificateFlags tlsErrors) { m_certificate = certificate; m_tlsErrors = tlsErrors; g_main_loop_quit(m_mainLoop); } void waitUntilSubresourceLoadFail() { g_main_loop_run(m_mainLoop); } GRefPtr m_certificate; GTlsCertificateFlags m_tlsErrors; }; static void testSubresourceLoadFailedWithTLSErrors(TLSSubresourceTest* test, gconstpointer) { WebKitWebContext* context = webkit_web_view_get_context(test->m_webView); webkit_web_context_set_tls_errors_policy(context, WEBKIT_TLS_ERRORS_POLICY_FAIL); assertIfSSLRequestProcessed = true; test->loadURI(kHttpServer->getURIForPath("/").data()); test->waitUntilSubresourceLoadFail(); g_assert(G_IS_TLS_CERTIFICATE(test->m_certificate.get())); g_assert_cmpuint(test->m_tlsErrors, ==, G_TLS_CERTIFICATE_UNKNOWN_CA); assertIfSSLRequestProcessed = false; } static void httpsServerCallback(SoupServer* server, SoupMessage* message, const char* path, GHashTable*, SoupClientContext*, gpointer) { if (message->method != SOUP_METHOD_GET) { soup_message_set_status(message, SOUP_STATUS_NOT_IMPLEMENTED); return; } g_assert(!assertIfSSLRequestProcessed); if (g_str_equal(path, "/")) { soup_message_set_status(message, SOUP_STATUS_OK); soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, indexHTML, strlen(indexHTML)); soup_message_body_complete(message->response_body); } else if (g_str_equal(path, "/insecure-content/")) { GUniquePtr responseHTML(g_strdup_printf(insecureContentHTML, kHttpServer->getURIForPath("/test-script").data(), kHttpServer->getURIForPath("/test-image").data())); soup_message_body_append(message->response_body, SOUP_MEMORY_COPY, responseHTML.get(), strlen(responseHTML.get())); soup_message_set_status(message, SOUP_STATUS_OK); soup_message_body_complete(message->response_body); } else if (g_str_equal(path, "/test-tls/")) { soup_message_set_status(message, SOUP_STATUS_OK); soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, TLSSuccessHTMLString, strlen(TLSSuccessHTMLString)); soup_message_body_complete(message->response_body); } else if (g_str_equal(path, "/redirect")) { soup_message_set_status(message, SOUP_STATUS_MOVED_PERMANENTLY); soup_message_headers_append(message->response_headers, "Location", kHttpServer->getURIForPath("/test-image").data()); } else if (g_str_equal(path, "/auth")) { soup_message_set_status(message, SOUP_STATUS_UNAUTHORIZED); soup_message_headers_append(message->response_headers, "WWW-Authenticate", "Basic realm=\"HTTPS auth\""); } else if (g_str_equal(path, "/style.css")) { soup_message_set_status(message, SOUP_STATUS_OK); static const char* styleCSS = "body { color: black; }"; soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, styleCSS, strlen(styleCSS)); } else soup_message_set_status(message, SOUP_STATUS_NOT_FOUND); } static void httpServerCallback(SoupServer* server, SoupMessage* message, const char* path, GHashTable*, SoupClientContext*, gpointer) { if (message->method != SOUP_METHOD_GET) { soup_message_set_status(message, SOUP_STATUS_NOT_IMPLEMENTED); return; } if (g_str_equal(path, "/test-script")) { GUniquePtr pathToFile(g_build_filename(Test::getResourcesDir().data(), "link-title.js", nullptr)); char* contents; gsize length; g_file_get_contents(pathToFile.get(), &contents, &length, 0); soup_message_body_append(message->response_body, SOUP_MEMORY_TAKE, contents, length); soup_message_set_status(message, SOUP_STATUS_OK); soup_message_body_complete(message->response_body); } else if (g_str_equal(path, "/test-image")) { GUniquePtr pathToFile(g_build_filename(Test::getResourcesDir().data(), "blank.ico", nullptr)); char* contents; gsize length; g_file_get_contents(pathToFile.get(), &contents, &length, 0); soup_message_body_append(message->response_body, SOUP_MEMORY_TAKE, contents, length); soup_message_set_status(message, SOUP_STATUS_OK); soup_message_body_complete(message->response_body); } else if (g_str_equal(path, "/")) { soup_message_set_status(message, SOUP_STATUS_OK); char* responseHTML = g_strdup_printf("SSL subresource test", kHttpsServer->getURIForPath("/style.css").data()); soup_message_body_append(message->response_body, SOUP_MEMORY_TAKE, responseHTML, strlen(responseHTML)); soup_message_body_complete(message->response_body); } else soup_message_set_status(message, SOUP_STATUS_NOT_FOUND); } void beforeAll() { kHttpsServer = new WebKitTestServer(WebKitTestServer::ServerHTTPS); kHttpsServer->run(httpsServerCallback); kHttpServer = new WebKitTestServer(WebKitTestServer::ServerHTTP); kHttpServer->run(httpServerCallback); SSLTest::add("WebKitWebView", "ssl", testSSL); InsecureContentTest::add("WebKitWebView", "insecure-content", testInsecureContent); SSLTest::add("WebKitWebView", "tls-errors-policy", testTLSErrorsPolicy); SSLTest::add("WebKitWebView", "tls-errors-redirect-to-http", testTLSErrorsRedirect); SSLTest::add("WebKitWebView", "tls-http-auth", testTLSErrorsHTTPAuth); TLSSubresourceTest::add("WebKitWebView", "tls-subresource", testSubresourceLoadFailedWithTLSErrors); TLSErrorsTest::add("WebKitWebView", "load-failed-with-tls-errors", testLoadFailedWithTLSErrors); } void afterAll() { delete kHttpsServer; delete kHttpServer; }