b351725f7676b54a77fdfd8d5cd4d9dfc4ccf784
[WebKit-https.git] / Tools / TestWebKitAPI / Tests / WebKit2Gtk / TestSSL.cpp
1 /*
2  * Copyright (C) 2012 Igalia S.L.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #include "config.h"
21
22 #include "LoadTrackingTest.h"
23 #include "WebKitTestServer.h"
24 #include <gtk/gtk.h>
25
26 static WebKitTestServer* kHttpsServer;
27 static WebKitTestServer* kHttpServer;
28
29 static const char* indexHTML = "<html><body>Testing WebKit2GTK+ SSL</body></htmll>";
30 static const char* insecureContentHTML = "<html><script src=\"%s\"></script><body><p>Text + image <img src=\"%s\" align=\"right\"/></p></body></html>";
31 static const char TLSExpectedSuccessTitle[] = "WebKit2Gtk+ TLS permission test";
32 static const char TLSSuccessHTMLString[] = "<html><head><title>WebKit2Gtk+ TLS permission test</title></head><body></body></html>";
33
34 class SSLTest: public LoadTrackingTest {
35 public:
36     MAKE_GLIB_TEST_FIXTURE(SSLTest);
37
38     SSLTest()
39         : m_tlsErrors(static_cast<GTlsCertificateFlags>(0))
40     {
41     }
42
43     virtual void provisionalLoadFailed(const gchar* failingURI, GError* error)
44     {
45         g_assert_error(error, SOUP_HTTP_ERROR, SOUP_STATUS_SSL_FAILED);
46         LoadTrackingTest::provisionalLoadFailed(failingURI, error);
47     }
48
49     virtual void loadCommitted()
50     {
51         GTlsCertificate* certificate = 0;
52         webkit_web_view_get_tls_info(m_webView, &certificate, &m_tlsErrors);
53         m_certificate = certificate;
54         LoadTrackingTest::loadCommitted();
55     }
56
57     void waitUntilLoadFinished()
58     {
59         m_certificate = 0;
60         m_tlsErrors = static_cast<GTlsCertificateFlags>(0);
61         LoadTrackingTest::waitUntilLoadFinished();
62     }
63
64     GRefPtr<GTlsCertificate> m_certificate;
65     GTlsCertificateFlags m_tlsErrors;
66 };
67
68 static void testSSL(SSLTest* test, gconstpointer)
69 {
70     test->loadURI(kHttpsServer->getURIForPath("/").data());
71     test->waitUntilLoadFinished();
72     g_assert(test->m_certificate);
73     // We always expect errors because we are using a self-signed certificate,
74     // but only G_TLS_CERTIFICATE_UNKNOWN_CA flags should be present.
75     g_assert(test->m_tlsErrors);
76     g_assert_cmpuint(test->m_tlsErrors, ==, G_TLS_CERTIFICATE_UNKNOWN_CA);
77
78     // Non HTTPS loads shouldn't have a certificate nor errors.
79     test->loadHtml(indexHTML, 0);
80     test->waitUntilLoadFinished();
81     g_assert(!test->m_certificate);
82     g_assert(!test->m_tlsErrors);
83 }
84
85 class InsecureContentTest: public WebViewTest {
86 public:
87     MAKE_GLIB_TEST_FIXTURE(InsecureContentTest);
88
89     InsecureContentTest()
90         : m_insecureContentRun(false)
91         , m_insecureContentDisplayed(false)
92     {
93         g_signal_connect(m_webView, "insecure-content-detected", G_CALLBACK(insecureContentDetectedCallback), this);
94     }
95
96     static void insecureContentDetectedCallback(WebKitWebView* webView, WebKitInsecureContentEvent event, InsecureContentTest* test)
97     {
98         g_assert(webView == test->m_webView);
99
100         if (event == WEBKIT_INSECURE_CONTENT_RUN)
101             test->m_insecureContentRun = true;
102
103         if (event == WEBKIT_INSECURE_CONTENT_DISPLAYED)
104             test->m_insecureContentDisplayed = true;
105     }
106
107     bool m_insecureContentRun;
108     bool m_insecureContentDisplayed;
109 };
110
111 static void testInsecureContent(InsecureContentTest* test, gconstpointer)
112 {
113     test->loadURI(kHttpsServer->getURIForPath("/insecure-content/").data());
114     test->waitUntilLoadFinished();
115
116     g_assert(test->m_insecureContentRun);
117     g_assert(test->m_insecureContentDisplayed);
118 }
119
120 static void testTLSErrorsPolicy(SSLTest* test, gconstpointer)
121 {
122     WebKitWebContext* context = webkit_web_view_get_context(test->m_webView);
123     // TLS errors are ignored by default.
124     g_assert(webkit_web_context_get_tls_errors_policy(context) == WEBKIT_TLS_ERRORS_POLICY_IGNORE);
125     test->loadURI(kHttpsServer->getURIForPath("/").data());
126     test->waitUntilLoadFinished();
127     g_assert(!test->m_loadFailed);
128
129     webkit_web_context_set_tls_errors_policy(context, WEBKIT_TLS_ERRORS_POLICY_FAIL);
130     test->loadURI(kHttpsServer->getURIForPath("/").data());
131     test->waitUntilLoadFinished();
132     g_assert(test->m_loadFailed);
133     g_assert(test->m_loadEvents.contains(LoadTrackingTest::ProvisionalLoadFailed));
134     g_assert(!test->m_loadEvents.contains(LoadTrackingTest::LoadCommitted));
135 }
136
137 static void testTLSErrorsRedirect(SSLTest* test, gconstpointer)
138 {
139     webkit_web_context_set_tls_errors_policy(webkit_web_view_get_context(test->m_webView), WEBKIT_TLS_ERRORS_POLICY_FAIL);
140     test->loadURI(kHttpsServer->getURIForPath("/redirect").data());
141     test->waitUntilLoadFinished();
142     g_assert(test->m_loadFailed);
143     g_assert(test->m_loadEvents.contains(LoadTrackingTest::ProvisionalLoadFailed));
144     g_assert(!test->m_loadEvents.contains(LoadTrackingTest::LoadCommitted));
145 }
146
147 class TLSErrorsTest: public SSLTest {
148 public:
149     MAKE_GLIB_TEST_FIXTURE(TLSErrorsTest);
150
151     TLSErrorsTest()
152         : m_tlsErrors(static_cast<GTlsCertificateFlags>(0))
153     {
154         g_signal_connect(m_webView, "load-failed-with-tls-errors", G_CALLBACK(runLoadFailedWithTLSErrorsCallback), this);
155     }
156
157     ~TLSErrorsTest()
158     {
159         g_signal_handlers_disconnect_matched(m_webView, G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, this);
160     }
161
162     static gboolean runLoadFailedWithTLSErrorsCallback(WebKitWebView*, GTlsCertificate* certificate, GTlsCertificateFlags tlsErrors, const char* host, TLSErrorsTest* test)
163     {
164         test->runLoadFailedWithTLSErrors(certificate, tlsErrors, host);
165         return TRUE;
166     }
167
168     void runLoadFailedWithTLSErrors(GTlsCertificate* certificate, GTlsCertificateFlags tlsErrors, const char* host)
169     {
170         assertObjectIsDeletedWhenTestFinishes(G_OBJECT(certificate));
171         m_certificate = certificate;
172         m_tlsErrors = tlsErrors;
173         m_host.reset(g_strdup(host));
174         g_main_loop_quit(m_mainLoop);
175     }
176
177     void waitUntilLoadFailedWithTLSErrors()
178     {
179         g_main_loop_run(m_mainLoop);
180     }
181
182     GTlsCertificate* certificate() const { return m_certificate.get(); }
183     GTlsCertificateFlags tlsErrors() const { return m_tlsErrors; }
184     const char* host() const { return m_host.get(); }
185
186 private:
187     GRefPtr<GTlsCertificate> m_certificate;
188     GTlsCertificateFlags m_tlsErrors;
189     GUniquePtr<char> m_host;
190 };
191
192 static void testLoadFailedWithTLSErrors(TLSErrorsTest* test, gconstpointer)
193 {
194     WebKitWebContext* context = webkit_web_view_get_context(test->m_webView);
195     webkit_web_context_set_tls_errors_policy(context, WEBKIT_TLS_ERRORS_POLICY_FAIL);
196
197     // The load-failed-with-tls-errors signal should be emitted when there is a TLS failure.
198     test->loadURI(kHttpsServer->getURIForPath("/test-tls/").data());
199     test->waitUntilLoadFailedWithTLSErrors();
200     // Test the WebKitCertificateInfo API.
201     g_assert(G_IS_TLS_CERTIFICATE(test->certificate()));
202     g_assert_cmpuint(test->tlsErrors(), ==, G_TLS_CERTIFICATE_UNKNOWN_CA);
203     g_assert_cmpstr(test->host(), ==, soup_uri_get_host(kHttpsServer->baseURI()));
204     g_assert_cmpint(test->m_loadEvents[0], ==, LoadTrackingTest::ProvisionalLoadStarted);
205     g_assert_cmpint(test->m_loadEvents[1], ==, LoadTrackingTest::LoadFinished);
206
207     // Test allowing an exception for this certificate on this host.
208     webkit_web_context_allow_tls_certificate_for_host(context, test->certificate(), test->host());
209     // The page should now load without errors.
210     test->loadURI(kHttpsServer->getURIForPath("/test-tls/").data());
211     test->waitUntilLoadFinished();
212
213     g_assert_cmpint(test->m_loadEvents[0], ==, LoadTrackingTest::ProvisionalLoadStarted);
214     g_assert_cmpint(test->m_loadEvents[1], ==, LoadTrackingTest::LoadCommitted);
215     g_assert_cmpint(test->m_loadEvents[2], ==, LoadTrackingTest::LoadFinished);
216     g_assert_cmpstr(webkit_web_view_get_title(test->m_webView), ==, TLSExpectedSuccessTitle);
217 }
218
219
220 static void httpsServerCallback(SoupServer* server, SoupMessage* message, const char* path, GHashTable*, SoupClientContext*, gpointer)
221 {
222     if (message->method != SOUP_METHOD_GET) {
223         soup_message_set_status(message, SOUP_STATUS_NOT_IMPLEMENTED);
224         return;
225     }
226
227     if (g_str_equal(path, "/")) {
228         soup_message_set_status(message, SOUP_STATUS_OK);
229         soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, indexHTML, strlen(indexHTML));
230         soup_message_body_complete(message->response_body);
231     } else if (g_str_equal(path, "/insecure-content/")) {
232         GUniquePtr<char> responseHTML(g_strdup_printf(insecureContentHTML, kHttpServer->getURIForPath("/test-script").data(), kHttpServer->getURIForPath("/test-image").data()));
233         soup_message_body_append(message->response_body, SOUP_MEMORY_COPY, responseHTML.get(), strlen(responseHTML.get()));
234         soup_message_set_status(message, SOUP_STATUS_OK);
235         soup_message_body_complete(message->response_body);
236     } else if (g_str_equal(path, "/test-tls/")) {
237         soup_message_set_status(message, SOUP_STATUS_OK);
238         soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, TLSSuccessHTMLString, strlen(TLSSuccessHTMLString));
239         soup_message_body_complete(message->response_body);
240     } else if (g_str_equal(path, "/redirect")) {
241         soup_message_set_status(message, SOUP_STATUS_MOVED_PERMANENTLY);
242         soup_message_headers_append(message->response_headers, "Location", kHttpServer->getURIForPath("/test-image").data());
243     } else
244         soup_message_set_status(message, SOUP_STATUS_NOT_FOUND);
245 }
246
247 static void httpServerCallback(SoupServer* server, SoupMessage* message, const char* path, GHashTable*, SoupClientContext*, gpointer)
248 {
249     if (message->method != SOUP_METHOD_GET) {
250         soup_message_set_status(message, SOUP_STATUS_NOT_IMPLEMENTED);
251         return;
252     }
253
254     if (g_str_equal(path, "/test-script")) {
255         GUniquePtr<char> pathToFile(g_build_filename(Test::getResourcesDir().data(), "link-title.js", nullptr));
256         char* contents;
257         gsize length;
258         g_file_get_contents(pathToFile.get(), &contents, &length, 0);
259
260         soup_message_body_append(message->response_body, SOUP_MEMORY_TAKE, contents, length);
261         soup_message_set_status(message, SOUP_STATUS_OK);
262         soup_message_body_complete(message->response_body);
263     } else if (g_str_equal(path, "/test-image")) {
264         GUniquePtr<char> pathToFile(g_build_filename(Test::getResourcesDir().data(), "blank.ico", nullptr));
265         char* contents;
266         gsize length;
267         g_file_get_contents(pathToFile.get(), &contents, &length, 0);
268
269         soup_message_body_append(message->response_body, SOUP_MEMORY_TAKE, contents, length);
270         soup_message_set_status(message, SOUP_STATUS_OK);
271         soup_message_body_complete(message->response_body);
272     } else
273         soup_message_set_status(message, SOUP_STATUS_NOT_FOUND);
274 }
275
276 void beforeAll()
277 {
278     kHttpsServer = new WebKitTestServer(WebKitTestServer::ServerHTTPS);
279     kHttpsServer->run(httpsServerCallback);
280
281     kHttpServer = new WebKitTestServer(WebKitTestServer::ServerHTTP);
282     kHttpServer->run(httpServerCallback);
283
284     SSLTest::add("WebKitWebView", "ssl", testSSL);
285     InsecureContentTest::add("WebKitWebView", "insecure-content", testInsecureContent);
286     // In this case the order of the tests does matter because tls-errors-policy tests the default policy,
287     // and expects that no exception will have been added for this certificate and host pair as is
288     // done in the tls-permission-request test.
289     SSLTest::add("WebKitWebView", "tls-errors-policy", testTLSErrorsPolicy);
290     SSLTest::add("WebKitWebView", "tls-errors-redirect-to-http", testTLSErrorsRedirect);
291     TLSErrorsTest::add("WebKitWebView", "load-failed-with-tls-errors", testLoadFailedWithTLSErrors);
292 }
293
294 void afterAll()
295 {
296     delete kHttpsServer;
297     delete kHttpServer;
298 }