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
win_cb_destroy(GtkWidget * win,struct browser_context * ctx)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
browser_update_title(struct browser_context * ctx)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
process_request_starting_uri(struct browser_context * ctx,const char * uri)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
view_cb_notify_estimated_load_progress(WebKitWebView * view,GParamSpec * pspec,struct browser_context * ctx)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
view_cb_resource_load_starting(WebKitWebView * view,WebKitWebResource * res,WebKitURIRequest * req,struct browser_context * ctx)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
view_cb_decide_policy(WebKitWebView * view,WebKitPolicyDecision * policy,WebKitPolicyDecisionType type,struct browser_context * ctx)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
view_cb_mouse_target_changed(WebKitWebView * view,WebKitHitTestResult * h,guint modifiers,struct browser_context * ctx)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
view_cb_notify_title(WebKitWebView * view,GParamSpec * ps,struct browser_context * ctx)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
view_cb_notify_progress(WebKitWebView * view,GParamSpec * pspec,struct browser_context * ctx)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
view_cb_notify_load_status(WebKitWebView * view,GParamSpec * pspec,struct browser_context * ctx)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
view_cb_resource_request_starting(WebKitWebView * view,WebKitWebFrame * frame,WebKitWebResource * res,WebKitNetworkRequest * req,WebKitNetworkResponse * resp,struct browser_context * ctx)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
view_cb_mime_type_policy_decision(WebKitWebView * view,WebKitWebFrame * frame,WebKitNetworkRequest * req,gchar * mime,WebKitWebPolicyDecision * policy,struct browser_context * ctx)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
view_cb_download_requested(WebKitWebView * view,WebKitDownload * dl,struct browser_context * ctx)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
view_cb_hovering_over_link(WebKitWebView * view,gchar * title,gchar * uri,struct browser_context * ctx)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
view_cb_title_changed(WebKitWebView * view,WebKitWebFrame * frame,const char * title,struct browser_context * ctx)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
hs20_web_browser(const char * url,int ignore_tls)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