1 #include <glib.h>
2 #include <glib/gstdio.h>
3
4 #include <stdio.h>
5 #include "test-utils.h"
6
7 #define DB_FILE "hsts-db.sqlite"
8
9 SoupURI *http_uri;
10 SoupURI *https_uri;
11
12 /* This server pseudo-implements the HSTS spec in order to allow us to
13 test the Soup HSTS feature.
14 */
15 static void
server_callback(SoupServer * server,SoupMessage * msg,const char * path,GHashTable * query,SoupClientContext * context,gpointer data)16 server_callback (SoupServer *server, SoupMessage *msg,
17 const char *path, GHashTable *query,
18 SoupClientContext *context, gpointer data)
19 {
20 const char *server_protocol = data;
21
22 if (strcmp (server_protocol, "http") == 0) {
23 char *uri_string;
24 SoupURI *uri = soup_uri_new ("https://localhost");
25 soup_uri_set_path (uri, path);
26 uri_string = soup_uri_to_string (uri, FALSE);
27 fprintf (stderr, "server is redirecting to HTTPS\n");
28 soup_message_set_redirect (msg, SOUP_STATUS_MOVED_PERMANENTLY, uri_string);
29 soup_uri_free (uri);
30 g_free (uri_string);
31 } else if (strcmp (server_protocol, "https") == 0) {
32 soup_message_set_status (msg, SOUP_STATUS_OK);
33 if (strcmp (path, "/long-lasting") == 0) {
34 soup_message_headers_append (msg->response_headers,
35 "Strict-Transport-Security",
36 "max-age=31536000");
37 } else if (strcmp (path, "/two-seconds") == 0) {
38 soup_message_headers_append (msg->response_headers,
39 "Strict-Transport-Security",
40 "max-age=2");
41 } else if (strcmp (path, "/delete") == 0) {
42 soup_message_headers_append (msg->response_headers,
43 "Strict-Transport-Security",
44 "max-age=0");
45 } else if (strcmp (path, "/subdomains") == 0) {
46 soup_message_headers_append (msg->response_headers,
47 "Strict-Transport-Security",
48 "max-age=31536000; includeSubDomains");
49 }
50 else if (strcmp (path, "/very-long-lasting") == 0) {
51 soup_message_headers_append (msg->response_headers,
52 "Strict-Transport-Security",
53 "max-age=631138519");
54 }
55 }
56 }
57
58 static void
session_get_uri(SoupSession * session,const char * uri,SoupStatus expected_status)59 session_get_uri (SoupSession *session, const char *uri, SoupStatus expected_status)
60 {
61 SoupMessage *msg;
62
63 msg = soup_message_new ("GET", uri);
64 soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
65 soup_session_send_message (session, msg);
66 soup_test_assert_message_status (msg, expected_status);
67 g_object_unref (msg);
68 }
69
70 /* The HSTS specification does not handle custom ports, so we need to
71 * rewrite the URI in the request and add the port where the server is
72 * listening before it is sent, to be able to connect to the localhost
73 * port where the server is actually running.
74 */
75 static void
rewrite_message_uri(SoupMessage * msg)76 rewrite_message_uri (SoupMessage *msg)
77 {
78 if (soup_uri_get_scheme (soup_message_get_uri (msg)) == SOUP_URI_SCHEME_HTTP)
79 soup_uri_set_port (soup_message_get_uri (msg), soup_uri_get_port (http_uri));
80 else if (soup_uri_get_scheme (soup_message_get_uri (msg)) == SOUP_URI_SCHEME_HTTPS)
81 soup_uri_set_port (soup_message_get_uri (msg), soup_uri_get_port (https_uri));
82 else
83 g_assert_not_reached();
84 }
85
86 static void
on_message_restarted(SoupMessage * msg,gpointer data)87 on_message_restarted (SoupMessage *msg,
88 gpointer data)
89 {
90 rewrite_message_uri (msg);
91 }
92
93 static void
on_request_queued(SoupSession * session,SoupMessage * msg,gpointer data)94 on_request_queued (SoupSession *session,
95 SoupMessage *msg,
96 gpointer data)
97 {
98 g_signal_connect (msg, "restarted", G_CALLBACK (on_message_restarted), NULL);
99
100 rewrite_message_uri (msg);
101 }
102
103 static SoupSession *
hsts_db_session_new(void)104 hsts_db_session_new (void)
105 {
106 SoupHSTSEnforcer *hsts_db = soup_hsts_enforcer_db_new (DB_FILE);
107
108 SoupSession *session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
109 SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
110 SOUP_SESSION_ADD_FEATURE, hsts_db,
111 NULL);
112 g_signal_connect (session, "request-queued", G_CALLBACK (on_request_queued), NULL);
113 g_object_unref (hsts_db);
114
115 return session;
116 }
117
118
119 static void
do_hsts_db_persistency_test(void)120 do_hsts_db_persistency_test (void)
121 {
122 SoupSession *session = hsts_db_session_new ();
123 session_get_uri (session, "https://localhost/long-lasting", SOUP_STATUS_OK);
124 session_get_uri (session, "http://localhost", SOUP_STATUS_OK);
125 soup_test_session_abort_unref (session);
126
127 session = hsts_db_session_new ();
128 session_get_uri (session, "http://localhost", SOUP_STATUS_OK);
129 soup_test_session_abort_unref (session);
130
131 g_remove (DB_FILE);
132 }
133
134 static void
do_hsts_db_subdomains_test(void)135 do_hsts_db_subdomains_test (void)
136 {
137 SoupSession *session = hsts_db_session_new ();
138 session_get_uri (session, "https://localhost/subdomains", SOUP_STATUS_OK);
139 soup_test_session_abort_unref (session);
140
141 session = hsts_db_session_new ();
142 session_get_uri (session, "http://subdomain.localhost", SOUP_STATUS_SSL_FAILED);
143 soup_test_session_abort_unref (session);
144
145 g_remove (DB_FILE);
146 }
147
148 static void
do_hsts_db_large_max_age_test(void)149 do_hsts_db_large_max_age_test (void)
150 {
151 SoupSession *session = hsts_db_session_new ();
152 session_get_uri (session, "https://localhost/very-long-lasting", SOUP_STATUS_OK);
153 session_get_uri (session, "http://localhost", SOUP_STATUS_OK);
154 soup_test_session_abort_unref (session);
155
156 session = hsts_db_session_new ();
157 session_get_uri (session, "http://localhost", SOUP_STATUS_OK);
158 soup_test_session_abort_unref (session);
159
160 g_remove (DB_FILE);
161 }
162
163 int
main(int argc,char ** argv)164 main (int argc, char **argv)
165 {
166 int ret;
167 SoupServer *server;
168 SoupServer *https_server = NULL;
169
170 test_init (argc, argv, NULL);
171
172 server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
173 soup_server_add_handler (server, NULL, server_callback, "http", NULL);
174 http_uri = soup_test_server_get_uri (server, "http", NULL);
175
176 if (tls_available) {
177 https_server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
178 soup_server_add_handler (https_server, NULL, server_callback, "https", NULL);
179 https_uri = soup_test_server_get_uri (https_server, "https", NULL);
180 }
181
182 g_test_add_func ("/hsts-db/basic", do_hsts_db_persistency_test);
183 g_test_add_func ("/hsts-db/subdomains", do_hsts_db_subdomains_test);
184 g_test_add_func ("/hsts-db/large-max-age", do_hsts_db_large_max_age_test);
185
186 ret = g_test_run ();
187
188 soup_uri_free (http_uri);
189 soup_test_server_quit_unref (server);
190
191 if (tls_available) {
192 soup_uri_free (https_uri);
193 soup_test_server_quit_unref (https_server);
194 }
195
196 test_cleanup ();
197 return ret;
198 }
199