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 #if WEBKIT_CHECK_VERSION(2, 32, 0) 374 WebKitWebContext *wkctx; 375 WebKitWebsiteDataManager *wkmgr; 376 377 wkctx = webkit_web_context_get_default(); 378 wkmgr = webkit_web_context_get_website_data_manager(wkctx); 379 webkit_website_data_manager_set_tls_errors_policy( 380 wkmgr, WEBKIT_TLS_ERRORS_POLICY_IGNORE); 381 #else 382 WebKitWebContext *wkctx; 383 384 wkctx = webkit_web_context_get_default(); 385 webkit_web_context_set_tls_errors_policy( 386 wkctx, WEBKIT_TLS_ERRORS_POLICY_IGNORE); 387 #endif 388 } 389 #endif /* USE_WEBKIT2 */ 390 391 webkit_web_view_load_uri(view, url); 392 393 ctx.gtk_main_started = 1; 394 gtk_main(); 395 gtk_widget_destroy(ctx.win); 396 while (gtk_events_pending()) 397 gtk_main_iteration(); 398 399 free(ctx.hover_link); 400 free(ctx.title); 401 return ctx.success; 402 } 403