xref: /freebsd/contrib/wpa/src/utils/browser.c (revision 1d386b48)
1 /*
2  * Hotspot 2.0 client - Web browser using WebKit
3  * Copyright (c) 2013, Qualcomm Atheros, Inc.
4  *
5  * This software may be distributed under the terms of the BSD license.
6  * See README for more details.
7  */
8 
9 #include "includes.h"
10 #ifdef USE_WEBKIT2
11 #include <webkit2/webkit2.h>
12 #else /* USE_WEBKIT2 */
13 #include <webkit/webkit.h>
14 #endif /* USE_WEBKIT2 */
15 
16 #include "common.h"
17 #include "browser.h"
18 
19 
20 struct browser_context {
21 	GtkWidget *win;
22 	WebKitWebView *view;
23 	int success;
24 	int progress;
25 	char *hover_link;
26 	char *title;
27 	int gtk_main_started;
28 	int quit_gtk_main;
29 };
30 
31 static void win_cb_destroy(GtkWidget *win, struct browser_context *ctx)
32 {
33 	wpa_printf(MSG_DEBUG, "BROWSER:%s", __func__);
34 	if (ctx->gtk_main_started)
35 		gtk_main_quit();
36 }
37 
38 
39 static void browser_update_title(struct browser_context *ctx)
40 {
41 	char buf[100];
42 
43 	if (ctx->hover_link) {
44 		gtk_window_set_title(GTK_WINDOW(ctx->win), ctx->hover_link);
45 		return;
46 	}
47 
48 	if (ctx->progress == 100) {
49 		gtk_window_set_title(GTK_WINDOW(ctx->win),
50 				     ctx->title ? ctx->title :
51 				     "Hotspot 2.0 client");
52 		return;
53 	}
54 
55 	snprintf(buf, sizeof(buf), "[%d%%] %s", ctx->progress,
56 		 ctx->title ? ctx->title : "Hotspot 2.0 client");
57 	gtk_window_set_title(GTK_WINDOW(ctx->win), buf);
58 }
59 
60 
61 static void process_request_starting_uri(struct browser_context *ctx,
62 					 const char *uri)
63 {
64 	int quit = 0;
65 
66 	if (g_str_has_prefix(uri, "osu://")) {
67 		ctx->success = atoi(uri + 6);
68 		quit = 1;
69 	} else if (g_str_has_prefix(uri, "http://localhost:12345")) {
70 		/*
71 		 * This is used as a special trigger to indicate that the
72 		 * user exchange has been completed.
73 		 */
74 		ctx->success = 1;
75 		quit = 1;
76 	}
77 
78 	if (quit) {
79 		if (ctx->gtk_main_started) {
80 			gtk_main_quit();
81 			ctx->gtk_main_started = 0;
82 		} else {
83 			ctx->quit_gtk_main = 1;
84 		}
85 	}
86 }
87 
88 
89 #ifdef USE_WEBKIT2
90 
91 static void view_cb_notify_estimated_load_progress(WebKitWebView *view,
92 						   GParamSpec *pspec,
93 						   struct browser_context *ctx)
94 {
95 	ctx->progress = 100 * webkit_web_view_get_estimated_load_progress(view);
96 	wpa_printf(MSG_DEBUG, "BROWSER:%s progress=%d", __func__,
97 		   ctx->progress);
98 	browser_update_title(ctx);
99 }
100 
101 
102 static void view_cb_resource_load_starting(WebKitWebView *view,
103 					   WebKitWebResource *res,
104 					   WebKitURIRequest *req,
105 					   struct browser_context *ctx)
106 {
107 	const gchar *uri = webkit_uri_request_get_uri(req);
108 
109 	wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri);
110 	process_request_starting_uri(ctx, uri);
111 }
112 
113 
114 static gboolean view_cb_decide_policy(WebKitWebView *view,
115 				      WebKitPolicyDecision *policy,
116 				      WebKitPolicyDecisionType type,
117 				      struct browser_context *ctx)
118 {
119 	wpa_printf(MSG_DEBUG, "BROWSER:%s type=%d", __func__, type);
120 	switch (type) {
121 	case WEBKIT_POLICY_DECISION_TYPE_RESPONSE: {
122 		/* This function makes webkit send a download signal for all
123 		 * unknown mime types. */
124 		WebKitResponsePolicyDecision *response;
125 
126 		response = WEBKIT_RESPONSE_POLICY_DECISION(policy);
127 		if (!webkit_response_policy_decision_is_mime_type_supported(
128 			    response)) {
129 			webkit_policy_decision_download(policy);
130 			return TRUE;
131 		}
132 		break;
133 	}
134 	case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION: {
135 		WebKitNavigationPolicyDecision *d;
136 		WebKitNavigationAction *a;
137 		WebKitURIRequest *req;
138 		const gchar *uri;
139 
140 		d = WEBKIT_NAVIGATION_POLICY_DECISION(policy);
141 		a = webkit_navigation_policy_decision_get_navigation_action(d);
142 		req = webkit_navigation_action_get_request(a);
143 		uri = webkit_uri_request_get_uri(req);
144 		wpa_printf(MSG_DEBUG, "BROWSER:%s navigation action: uri=%s",
145 			   __func__, uri);
146 		process_request_starting_uri(ctx, uri);
147 		break;
148 	}
149 	default:
150 		break;
151 	}
152 
153 	return FALSE;
154 }
155 
156 
157 static void view_cb_mouse_target_changed(WebKitWebView *view,
158 					 WebKitHitTestResult *h,
159 					 guint modifiers,
160 					 struct browser_context *ctx)
161 {
162 	WebKitHitTestResultContext hc = webkit_hit_test_result_get_context(h);
163 	const char *uri = NULL;
164 
165 	if (hc & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK)
166 		uri = webkit_hit_test_result_get_link_uri(h);
167 	else if (hc & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE)
168 		uri = webkit_hit_test_result_get_image_uri(h);
169 	else if (hc & WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA)
170 		uri = webkit_hit_test_result_get_media_uri(h);
171 
172 	wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri ? uri : "N/A");
173 	os_free(ctx->hover_link);
174 	if (uri)
175 		ctx->hover_link = os_strdup(uri);
176 	else
177 		ctx->hover_link = NULL;
178 
179 	browser_update_title(ctx);
180 }
181 
182 
183 static void view_cb_notify_title(WebKitWebView *view, GParamSpec *ps,
184 				 struct browser_context *ctx)
185 {
186 	const char *title;
187 
188 	title = webkit_web_view_get_title(ctx->view);
189 	wpa_printf(MSG_DEBUG, "BROWSER:%s title=%s", __func__, title);
190 	os_free(ctx->title);
191 	ctx->title = os_strdup(title);
192 	browser_update_title(ctx);
193 }
194 
195 #else /* USE_WEBKIT2 */
196 
197 static void view_cb_notify_progress(WebKitWebView *view, GParamSpec *pspec,
198 				    struct browser_context *ctx)
199 {
200 	ctx->progress = 100 * webkit_web_view_get_progress(view);
201 	wpa_printf(MSG_DEBUG, "BROWSER:%s progress=%d", __func__,
202 		   ctx->progress);
203 	browser_update_title(ctx);
204 }
205 
206 
207 static void view_cb_notify_load_status(WebKitWebView *view, GParamSpec *pspec,
208 				       struct browser_context *ctx)
209 {
210 	int status = webkit_web_view_get_load_status(view);
211 	wpa_printf(MSG_DEBUG, "BROWSER:%s load-status=%d uri=%s",
212 		   __func__, status, webkit_web_view_get_uri(view));
213 	if (ctx->quit_gtk_main) {
214 		gtk_main_quit();
215 		ctx->gtk_main_started = 0;
216 	}
217 }
218 
219 
220 static void view_cb_resource_request_starting(WebKitWebView *view,
221 					      WebKitWebFrame *frame,
222 					      WebKitWebResource *res,
223 					      WebKitNetworkRequest *req,
224 					      WebKitNetworkResponse *resp,
225 					      struct browser_context *ctx)
226 {
227 	const gchar *uri = webkit_network_request_get_uri(req);
228 
229 	wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri);
230 	if (g_str_has_suffix(uri, "/favicon.ico"))
231 		webkit_network_request_set_uri(req, "about:blank");
232 
233 	process_request_starting_uri(ctx, uri);
234 }
235 
236 
237 static gboolean view_cb_mime_type_policy_decision(
238 	WebKitWebView *view, WebKitWebFrame *frame, WebKitNetworkRequest *req,
239 	gchar *mime, WebKitWebPolicyDecision *policy,
240 	struct browser_context *ctx)
241 {
242 	wpa_printf(MSG_DEBUG, "BROWSER:%s mime=%s", __func__, mime);
243 
244 	if (!webkit_web_view_can_show_mime_type(view, mime)) {
245 		webkit_web_policy_decision_download(policy);
246 		return TRUE;
247 	}
248 
249 	return FALSE;
250 }
251 
252 
253 static gboolean view_cb_download_requested(WebKitWebView *view,
254 					   WebKitDownload *dl,
255 					   struct browser_context *ctx)
256 {
257 	const gchar *uri;
258 	uri = webkit_download_get_uri(dl);
259 	wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri);
260 	return FALSE;
261 }
262 
263 
264 static void view_cb_hovering_over_link(WebKitWebView *view, gchar *title,
265 				       gchar *uri, struct browser_context *ctx)
266 {
267 	wpa_printf(MSG_DEBUG, "BROWSER:%s title=%s uri=%s", __func__, title,
268 		   uri);
269 	os_free(ctx->hover_link);
270 	if (uri)
271 		ctx->hover_link = os_strdup(uri);
272 	else
273 		ctx->hover_link = NULL;
274 
275 	browser_update_title(ctx);
276 }
277 
278 
279 static void view_cb_title_changed(WebKitWebView *view, WebKitWebFrame *frame,
280 				  const char *title,
281 				  struct browser_context *ctx)
282 {
283 	wpa_printf(MSG_DEBUG, "BROWSER:%s title=%s", __func__, title);
284 	os_free(ctx->title);
285 	ctx->title = os_strdup(title);
286 	browser_update_title(ctx);
287 }
288 
289 #endif /* USE_WEBKIT2 */
290 
291 
292 int hs20_web_browser(const char *url, int ignore_tls)
293 {
294 	GtkWidget *scroll;
295 	WebKitWebView *view;
296 #ifdef USE_WEBKIT2
297 	WebKitSettings *settings;
298 #else /* USE_WEBKIT2 */
299 	WebKitWebSettings *settings;
300 	SoupSession *s;
301 #endif /* USE_WEBKIT2 */
302 	struct browser_context ctx;
303 
304 	memset(&ctx, 0, sizeof(ctx));
305 	if (!gtk_init_check(NULL, NULL))
306 		return -1;
307 
308 #ifndef USE_WEBKIT2
309 	s = webkit_get_default_session();
310 	g_object_set(G_OBJECT(s), "ssl-ca-file",
311 		     "/etc/ssl/certs/ca-certificates.crt", NULL);
312 	if (ignore_tls)
313 		g_object_set(G_OBJECT(s), "ssl-strict", FALSE, NULL);
314 #endif /* USE_WEBKIT2 */
315 
316 	ctx.win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
317 	gtk_window_set_role(GTK_WINDOW(ctx.win), "Hotspot 2.0 client");
318 	gtk_window_set_default_size(GTK_WINDOW(ctx.win), 800, 600);
319 
320 	scroll = gtk_scrolled_window_new(NULL, NULL);
321 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
322 				       GTK_POLICY_NEVER, GTK_POLICY_NEVER);
323 
324 	g_signal_connect(G_OBJECT(ctx.win), "destroy",
325 			 G_CALLBACK(win_cb_destroy), &ctx);
326 
327 	view = WEBKIT_WEB_VIEW(webkit_web_view_new());
328 	ctx.view = view;
329 #ifdef USE_WEBKIT2
330 	g_signal_connect(G_OBJECT(view), "notify::estimated-load-progress",
331 			 G_CALLBACK(view_cb_notify_estimated_load_progress),
332 			 &ctx);
333 	g_signal_connect(G_OBJECT(view), "resource-load-started",
334 			 G_CALLBACK(view_cb_resource_load_starting), &ctx);
335 	g_signal_connect(G_OBJECT(view), "decide-policy",
336 			 G_CALLBACK(view_cb_decide_policy), &ctx);
337 	g_signal_connect(G_OBJECT(view), "mouse-target-changed",
338 			 G_CALLBACK(view_cb_mouse_target_changed), &ctx);
339 	g_signal_connect(G_OBJECT(view), "notify::title",
340 			 G_CALLBACK(view_cb_notify_title), &ctx);
341 #else /* USE_WEBKIT2 */
342 	g_signal_connect(G_OBJECT(view), "notify::load-status",
343 			 G_CALLBACK(view_cb_notify_load_status), &ctx);
344 	g_signal_connect(G_OBJECT(view), "notify::progress",
345 			 G_CALLBACK(view_cb_notify_progress), &ctx);
346 	g_signal_connect(G_OBJECT(view), "resource-request-starting",
347 			 G_CALLBACK(view_cb_resource_request_starting), &ctx);
348 	g_signal_connect(G_OBJECT(view), "mime-type-policy-decision-requested",
349 			 G_CALLBACK(view_cb_mime_type_policy_decision), &ctx);
350 	g_signal_connect(G_OBJECT(view), "download-requested",
351 			 G_CALLBACK(view_cb_download_requested), &ctx);
352 	g_signal_connect(G_OBJECT(view), "hovering-over-link",
353 			 G_CALLBACK(view_cb_hovering_over_link), &ctx);
354 	g_signal_connect(G_OBJECT(view), "title-changed",
355 			 G_CALLBACK(view_cb_title_changed), &ctx);
356 #endif /* USE_WEBKIT2 */
357 
358 	gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(view));
359 	gtk_container_add(GTK_CONTAINER(ctx.win), GTK_WIDGET(scroll));
360 
361 	gtk_widget_grab_focus(GTK_WIDGET(view));
362 	gtk_widget_show_all(ctx.win);
363 
364 	settings = webkit_web_view_get_settings(view);
365 	g_object_set(G_OBJECT(settings), "user-agent",
366 		     "Mozilla/5.0 (X11; U; Unix; en-US) "
367 		     "AppleWebKit/537.15 (KHTML, like Gecko) "
368 		     "hs20-client/1.0", NULL);
369 	g_object_set(G_OBJECT(settings), "auto-load-images", TRUE, NULL);
370 
371 #ifdef USE_WEBKIT2
372 	if (ignore_tls) {
373 		WebKitWebContext *wkctx;
374 
375 		wkctx = webkit_web_context_get_default();
376 		webkit_web_context_set_tls_errors_policy(
377 			wkctx, WEBKIT_TLS_ERRORS_POLICY_IGNORE);
378 	}
379 #endif /* USE_WEBKIT2 */
380 
381 	webkit_web_view_load_uri(view, url);
382 
383 	ctx.gtk_main_started = 1;
384 	gtk_main();
385 	gtk_widget_destroy(ctx.win);
386 	while (gtk_events_pending())
387 		gtk_main_iteration();
388 
389 	free(ctx.hover_link);
390 	free(ctx.title);
391 	return ctx.success;
392 }
393