13c260e60Schristos /* 23c260e60Schristos * Hotspot 2.0 client - Web browser using WebKit 33c260e60Schristos * Copyright (c) 2013, Qualcomm Atheros, Inc. 43c260e60Schristos * 53c260e60Schristos * This software may be distributed under the terms of the BSD license. 63c260e60Schristos * See README for more details. 73c260e60Schristos */ 83c260e60Schristos 93c260e60Schristos #include "includes.h" 10*bb618362Schristos #ifdef USE_WEBKIT2 11*bb618362Schristos #include <webkit2/webkit2.h> 12*bb618362Schristos #else /* USE_WEBKIT2 */ 133c260e60Schristos #include <webkit/webkit.h> 14*bb618362Schristos #endif /* USE_WEBKIT2 */ 153c260e60Schristos 163c260e60Schristos #include "common.h" 173c260e60Schristos #include "browser.h" 183c260e60Schristos 193c260e60Schristos 203c260e60Schristos struct browser_context { 213c260e60Schristos GtkWidget *win; 22*bb618362Schristos WebKitWebView *view; 233c260e60Schristos int success; 243c260e60Schristos int progress; 253c260e60Schristos char *hover_link; 263c260e60Schristos char *title; 27*bb618362Schristos int gtk_main_started; 28*bb618362Schristos int quit_gtk_main; 293c260e60Schristos }; 303c260e60Schristos 313c260e60Schristos static void win_cb_destroy(GtkWidget *win, struct browser_context *ctx) 323c260e60Schristos { 333c260e60Schristos wpa_printf(MSG_DEBUG, "BROWSER:%s", __func__); 34*bb618362Schristos if (ctx->gtk_main_started) 353c260e60Schristos gtk_main_quit(); 363c260e60Schristos } 373c260e60Schristos 383c260e60Schristos 393c260e60Schristos static void browser_update_title(struct browser_context *ctx) 403c260e60Schristos { 413c260e60Schristos char buf[100]; 423c260e60Schristos 433c260e60Schristos if (ctx->hover_link) { 443c260e60Schristos gtk_window_set_title(GTK_WINDOW(ctx->win), ctx->hover_link); 453c260e60Schristos return; 463c260e60Schristos } 473c260e60Schristos 483c260e60Schristos if (ctx->progress == 100) { 493c260e60Schristos gtk_window_set_title(GTK_WINDOW(ctx->win), 503c260e60Schristos ctx->title ? ctx->title : 513c260e60Schristos "Hotspot 2.0 client"); 523c260e60Schristos return; 533c260e60Schristos } 543c260e60Schristos 553c260e60Schristos snprintf(buf, sizeof(buf), "[%d%%] %s", ctx->progress, 563c260e60Schristos ctx->title ? ctx->title : "Hotspot 2.0 client"); 573c260e60Schristos gtk_window_set_title(GTK_WINDOW(ctx->win), buf); 583c260e60Schristos } 593c260e60Schristos 603c260e60Schristos 61*bb618362Schristos static void process_request_starting_uri(struct browser_context *ctx, 62*bb618362Schristos const char *uri) 63*bb618362Schristos { 64*bb618362Schristos int quit = 0; 65*bb618362Schristos 66*bb618362Schristos if (g_str_has_prefix(uri, "osu://")) { 67*bb618362Schristos ctx->success = atoi(uri + 6); 68*bb618362Schristos quit = 1; 69*bb618362Schristos } else if (g_str_has_prefix(uri, "http://localhost:12345")) { 70*bb618362Schristos /* 71*bb618362Schristos * This is used as a special trigger to indicate that the 72*bb618362Schristos * user exchange has been completed. 73*bb618362Schristos */ 74*bb618362Schristos ctx->success = 1; 75*bb618362Schristos quit = 1; 76*bb618362Schristos } 77*bb618362Schristos 78*bb618362Schristos if (quit) { 79*bb618362Schristos if (ctx->gtk_main_started) { 80*bb618362Schristos gtk_main_quit(); 81*bb618362Schristos ctx->gtk_main_started = 0; 82*bb618362Schristos } else { 83*bb618362Schristos ctx->quit_gtk_main = 1; 84*bb618362Schristos } 85*bb618362Schristos } 86*bb618362Schristos } 87*bb618362Schristos 88*bb618362Schristos 89*bb618362Schristos #ifdef USE_WEBKIT2 90*bb618362Schristos 91*bb618362Schristos static void view_cb_notify_estimated_load_progress(WebKitWebView *view, 92*bb618362Schristos GParamSpec *pspec, 93*bb618362Schristos struct browser_context *ctx) 94*bb618362Schristos { 95*bb618362Schristos ctx->progress = 100 * webkit_web_view_get_estimated_load_progress(view); 96*bb618362Schristos wpa_printf(MSG_DEBUG, "BROWSER:%s progress=%d", __func__, 97*bb618362Schristos ctx->progress); 98*bb618362Schristos browser_update_title(ctx); 99*bb618362Schristos } 100*bb618362Schristos 101*bb618362Schristos 102*bb618362Schristos static void view_cb_resource_load_starting(WebKitWebView *view, 103*bb618362Schristos WebKitWebResource *res, 104*bb618362Schristos WebKitURIRequest *req, 105*bb618362Schristos struct browser_context *ctx) 106*bb618362Schristos { 107*bb618362Schristos const gchar *uri = webkit_uri_request_get_uri(req); 108*bb618362Schristos 109*bb618362Schristos wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri); 110*bb618362Schristos process_request_starting_uri(ctx, uri); 111*bb618362Schristos } 112*bb618362Schristos 113*bb618362Schristos 114*bb618362Schristos static gboolean view_cb_decide_policy(WebKitWebView *view, 115*bb618362Schristos WebKitPolicyDecision *policy, 116*bb618362Schristos WebKitPolicyDecisionType type, 117*bb618362Schristos struct browser_context *ctx) 118*bb618362Schristos { 119*bb618362Schristos wpa_printf(MSG_DEBUG, "BROWSER:%s type=%d", __func__, type); 120*bb618362Schristos switch (type) { 121*bb618362Schristos case WEBKIT_POLICY_DECISION_TYPE_RESPONSE: { 122*bb618362Schristos /* This function makes webkit send a download signal for all 123*bb618362Schristos * unknown mime types. */ 124*bb618362Schristos WebKitResponsePolicyDecision *response; 125*bb618362Schristos 126*bb618362Schristos response = WEBKIT_RESPONSE_POLICY_DECISION(policy); 127*bb618362Schristos if (!webkit_response_policy_decision_is_mime_type_supported( 128*bb618362Schristos response)) { 129*bb618362Schristos webkit_policy_decision_download(policy); 130*bb618362Schristos return TRUE; 131*bb618362Schristos } 132*bb618362Schristos break; 133*bb618362Schristos } 134*bb618362Schristos case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION: { 135*bb618362Schristos WebKitNavigationPolicyDecision *d; 136*bb618362Schristos WebKitNavigationAction *a; 137*bb618362Schristos WebKitURIRequest *req; 138*bb618362Schristos const gchar *uri; 139*bb618362Schristos 140*bb618362Schristos d = WEBKIT_NAVIGATION_POLICY_DECISION(policy); 141*bb618362Schristos a = webkit_navigation_policy_decision_get_navigation_action(d); 142*bb618362Schristos req = webkit_navigation_action_get_request(a); 143*bb618362Schristos uri = webkit_uri_request_get_uri(req); 144*bb618362Schristos wpa_printf(MSG_DEBUG, "BROWSER:%s navigation action: uri=%s", 145*bb618362Schristos __func__, uri); 146*bb618362Schristos process_request_starting_uri(ctx, uri); 147*bb618362Schristos break; 148*bb618362Schristos } 149*bb618362Schristos default: 150*bb618362Schristos break; 151*bb618362Schristos } 152*bb618362Schristos 153*bb618362Schristos return FALSE; 154*bb618362Schristos } 155*bb618362Schristos 156*bb618362Schristos 157*bb618362Schristos static void view_cb_mouse_target_changed(WebKitWebView *view, 158*bb618362Schristos WebKitHitTestResult *h, 159*bb618362Schristos guint modifiers, 160*bb618362Schristos struct browser_context *ctx) 161*bb618362Schristos { 162*bb618362Schristos WebKitHitTestResultContext hc = webkit_hit_test_result_get_context(h); 163*bb618362Schristos const char *uri = NULL; 164*bb618362Schristos 165*bb618362Schristos if (hc & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) 166*bb618362Schristos uri = webkit_hit_test_result_get_link_uri(h); 167*bb618362Schristos else if (hc & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) 168*bb618362Schristos uri = webkit_hit_test_result_get_image_uri(h); 169*bb618362Schristos else if (hc & WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA) 170*bb618362Schristos uri = webkit_hit_test_result_get_media_uri(h); 171*bb618362Schristos 172*bb618362Schristos wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri ? uri : "N/A"); 173*bb618362Schristos os_free(ctx->hover_link); 174*bb618362Schristos if (uri) 175*bb618362Schristos ctx->hover_link = os_strdup(uri); 176*bb618362Schristos else 177*bb618362Schristos ctx->hover_link = NULL; 178*bb618362Schristos 179*bb618362Schristos browser_update_title(ctx); 180*bb618362Schristos } 181*bb618362Schristos 182*bb618362Schristos 183*bb618362Schristos static void view_cb_notify_title(WebKitWebView *view, GParamSpec *ps, 184*bb618362Schristos struct browser_context *ctx) 185*bb618362Schristos { 186*bb618362Schristos const char *title; 187*bb618362Schristos 188*bb618362Schristos title = webkit_web_view_get_title(ctx->view); 189*bb618362Schristos wpa_printf(MSG_DEBUG, "BROWSER:%s title=%s", __func__, title); 190*bb618362Schristos os_free(ctx->title); 191*bb618362Schristos ctx->title = os_strdup(title); 192*bb618362Schristos browser_update_title(ctx); 193*bb618362Schristos } 194*bb618362Schristos 195*bb618362Schristos #else /* USE_WEBKIT2 */ 196*bb618362Schristos 1973c260e60Schristos static void view_cb_notify_progress(WebKitWebView *view, GParamSpec *pspec, 1983c260e60Schristos struct browser_context *ctx) 1993c260e60Schristos { 2003c260e60Schristos ctx->progress = 100 * webkit_web_view_get_progress(view); 2013c260e60Schristos wpa_printf(MSG_DEBUG, "BROWSER:%s progress=%d", __func__, 2023c260e60Schristos ctx->progress); 2033c260e60Schristos browser_update_title(ctx); 2043c260e60Schristos } 2053c260e60Schristos 2063c260e60Schristos 2073c260e60Schristos static void view_cb_notify_load_status(WebKitWebView *view, GParamSpec *pspec, 2083c260e60Schristos struct browser_context *ctx) 2093c260e60Schristos { 2103c260e60Schristos int status = webkit_web_view_get_load_status(view); 2113c260e60Schristos wpa_printf(MSG_DEBUG, "BROWSER:%s load-status=%d uri=%s", 2123c260e60Schristos __func__, status, webkit_web_view_get_uri(view)); 213*bb618362Schristos if (ctx->quit_gtk_main) { 214*bb618362Schristos gtk_main_quit(); 215*bb618362Schristos ctx->gtk_main_started = 0; 216*bb618362Schristos } 2173c260e60Schristos } 2183c260e60Schristos 2193c260e60Schristos 2203c260e60Schristos static void view_cb_resource_request_starting(WebKitWebView *view, 2213c260e60Schristos WebKitWebFrame *frame, 2223c260e60Schristos WebKitWebResource *res, 2233c260e60Schristos WebKitNetworkRequest *req, 2243c260e60Schristos WebKitNetworkResponse *resp, 2253c260e60Schristos struct browser_context *ctx) 2263c260e60Schristos { 2273c260e60Schristos const gchar *uri = webkit_network_request_get_uri(req); 228*bb618362Schristos 2293c260e60Schristos wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri); 2303c260e60Schristos if (g_str_has_suffix(uri, "/favicon.ico")) 2313c260e60Schristos webkit_network_request_set_uri(req, "about:blank"); 232*bb618362Schristos 233*bb618362Schristos process_request_starting_uri(ctx, uri); 2343c260e60Schristos } 2353c260e60Schristos 2363c260e60Schristos 2373c260e60Schristos static gboolean view_cb_mime_type_policy_decision( 2383c260e60Schristos WebKitWebView *view, WebKitWebFrame *frame, WebKitNetworkRequest *req, 2393c260e60Schristos gchar *mime, WebKitWebPolicyDecision *policy, 2403c260e60Schristos struct browser_context *ctx) 2413c260e60Schristos { 2423c260e60Schristos wpa_printf(MSG_DEBUG, "BROWSER:%s mime=%s", __func__, mime); 2433c260e60Schristos 2443c260e60Schristos if (!webkit_web_view_can_show_mime_type(view, mime)) { 2453c260e60Schristos webkit_web_policy_decision_download(policy); 2463c260e60Schristos return TRUE; 2473c260e60Schristos } 2483c260e60Schristos 2493c260e60Schristos return FALSE; 2503c260e60Schristos } 2513c260e60Schristos 2523c260e60Schristos 2533c260e60Schristos static gboolean view_cb_download_requested(WebKitWebView *view, 2543c260e60Schristos WebKitDownload *dl, 2553c260e60Schristos struct browser_context *ctx) 2563c260e60Schristos { 2573c260e60Schristos const gchar *uri; 2583c260e60Schristos uri = webkit_download_get_uri(dl); 2593c260e60Schristos wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri); 2603c260e60Schristos return FALSE; 2613c260e60Schristos } 2623c260e60Schristos 2633c260e60Schristos 2643c260e60Schristos static void view_cb_hovering_over_link(WebKitWebView *view, gchar *title, 2653c260e60Schristos gchar *uri, struct browser_context *ctx) 2663c260e60Schristos { 2673c260e60Schristos wpa_printf(MSG_DEBUG, "BROWSER:%s title=%s uri=%s", __func__, title, 2683c260e60Schristos uri); 2693c260e60Schristos os_free(ctx->hover_link); 2703c260e60Schristos if (uri) 2713c260e60Schristos ctx->hover_link = os_strdup(uri); 2723c260e60Schristos else 2733c260e60Schristos ctx->hover_link = NULL; 2743c260e60Schristos 2753c260e60Schristos browser_update_title(ctx); 2763c260e60Schristos } 2773c260e60Schristos 2783c260e60Schristos 2793c260e60Schristos static void view_cb_title_changed(WebKitWebView *view, WebKitWebFrame *frame, 2803c260e60Schristos const char *title, 2813c260e60Schristos struct browser_context *ctx) 2823c260e60Schristos { 2833c260e60Schristos wpa_printf(MSG_DEBUG, "BROWSER:%s title=%s", __func__, title); 2843c260e60Schristos os_free(ctx->title); 2853c260e60Schristos ctx->title = os_strdup(title); 2863c260e60Schristos browser_update_title(ctx); 2873c260e60Schristos } 2883c260e60Schristos 289*bb618362Schristos #endif /* USE_WEBKIT2 */ 2903c260e60Schristos 291*bb618362Schristos 292*bb618362Schristos int hs20_web_browser(const char *url, int ignore_tls) 2933c260e60Schristos { 2943c260e60Schristos GtkWidget *scroll; 2953c260e60Schristos WebKitWebView *view; 296*bb618362Schristos #ifdef USE_WEBKIT2 297*bb618362Schristos WebKitSettings *settings; 298*bb618362Schristos #else /* USE_WEBKIT2 */ 2993c260e60Schristos WebKitWebSettings *settings; 300*bb618362Schristos SoupSession *s; 301*bb618362Schristos #endif /* USE_WEBKIT2 */ 3023c260e60Schristos struct browser_context ctx; 3033c260e60Schristos 3043c260e60Schristos memset(&ctx, 0, sizeof(ctx)); 3053c260e60Schristos if (!gtk_init_check(NULL, NULL)) 3063c260e60Schristos return -1; 3073c260e60Schristos 308*bb618362Schristos #ifndef USE_WEBKIT2 3093c260e60Schristos s = webkit_get_default_session(); 3103c260e60Schristos g_object_set(G_OBJECT(s), "ssl-ca-file", 3113c260e60Schristos "/etc/ssl/certs/ca-certificates.crt", NULL); 312*bb618362Schristos if (ignore_tls) 3133c260e60Schristos g_object_set(G_OBJECT(s), "ssl-strict", FALSE, NULL); 314*bb618362Schristos #endif /* USE_WEBKIT2 */ 3153c260e60Schristos 3163c260e60Schristos ctx.win = gtk_window_new(GTK_WINDOW_TOPLEVEL); 3173d6c0713Schristos gtk_window_set_role(GTK_WINDOW(ctx.win), "Hotspot 2.0 client"); 3183c260e60Schristos gtk_window_set_default_size(GTK_WINDOW(ctx.win), 800, 600); 3193c260e60Schristos 3203c260e60Schristos scroll = gtk_scrolled_window_new(NULL, NULL); 3213c260e60Schristos gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), 3223c260e60Schristos GTK_POLICY_NEVER, GTK_POLICY_NEVER); 3233c260e60Schristos 3243c260e60Schristos g_signal_connect(G_OBJECT(ctx.win), "destroy", 3253c260e60Schristos G_CALLBACK(win_cb_destroy), &ctx); 3263c260e60Schristos 3273c260e60Schristos view = WEBKIT_WEB_VIEW(webkit_web_view_new()); 328*bb618362Schristos ctx.view = view; 329*bb618362Schristos #ifdef USE_WEBKIT2 330*bb618362Schristos g_signal_connect(G_OBJECT(view), "notify::estimated-load-progress", 331*bb618362Schristos G_CALLBACK(view_cb_notify_estimated_load_progress), 332*bb618362Schristos &ctx); 333*bb618362Schristos g_signal_connect(G_OBJECT(view), "resource-load-started", 334*bb618362Schristos G_CALLBACK(view_cb_resource_load_starting), &ctx); 335*bb618362Schristos g_signal_connect(G_OBJECT(view), "decide-policy", 336*bb618362Schristos G_CALLBACK(view_cb_decide_policy), &ctx); 337*bb618362Schristos g_signal_connect(G_OBJECT(view), "mouse-target-changed", 338*bb618362Schristos G_CALLBACK(view_cb_mouse_target_changed), &ctx); 339*bb618362Schristos g_signal_connect(G_OBJECT(view), "notify::title", 340*bb618362Schristos G_CALLBACK(view_cb_notify_title), &ctx); 341*bb618362Schristos #else /* USE_WEBKIT2 */ 3423c260e60Schristos g_signal_connect(G_OBJECT(view), "notify::load-status", 3433c260e60Schristos G_CALLBACK(view_cb_notify_load_status), &ctx); 344*bb618362Schristos g_signal_connect(G_OBJECT(view), "notify::progress", 345*bb618362Schristos G_CALLBACK(view_cb_notify_progress), &ctx); 3463c260e60Schristos g_signal_connect(G_OBJECT(view), "resource-request-starting", 3473c260e60Schristos G_CALLBACK(view_cb_resource_request_starting), &ctx); 3483c260e60Schristos g_signal_connect(G_OBJECT(view), "mime-type-policy-decision-requested", 3493c260e60Schristos G_CALLBACK(view_cb_mime_type_policy_decision), &ctx); 3503c260e60Schristos g_signal_connect(G_OBJECT(view), "download-requested", 3513c260e60Schristos G_CALLBACK(view_cb_download_requested), &ctx); 3523c260e60Schristos g_signal_connect(G_OBJECT(view), "hovering-over-link", 3533c260e60Schristos G_CALLBACK(view_cb_hovering_over_link), &ctx); 3543c260e60Schristos g_signal_connect(G_OBJECT(view), "title-changed", 3553c260e60Schristos G_CALLBACK(view_cb_title_changed), &ctx); 356*bb618362Schristos #endif /* USE_WEBKIT2 */ 3573c260e60Schristos 3583c260e60Schristos gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(view)); 3593c260e60Schristos gtk_container_add(GTK_CONTAINER(ctx.win), GTK_WIDGET(scroll)); 3603c260e60Schristos 3613c260e60Schristos gtk_widget_grab_focus(GTK_WIDGET(view)); 3623c260e60Schristos gtk_widget_show_all(ctx.win); 3633c260e60Schristos 3643c260e60Schristos settings = webkit_web_view_get_settings(view); 3653c260e60Schristos g_object_set(G_OBJECT(settings), "user-agent", 3663c260e60Schristos "Mozilla/5.0 (X11; U; Unix; en-US) " 3673c260e60Schristos "AppleWebKit/537.15 (KHTML, like Gecko) " 3683c260e60Schristos "hs20-client/1.0", NULL); 3693c260e60Schristos g_object_set(G_OBJECT(settings), "auto-load-images", TRUE, NULL); 3703c260e60Schristos 371*bb618362Schristos #ifdef USE_WEBKIT2 372*bb618362Schristos if (ignore_tls) { 373*bb618362Schristos #if WEBKIT_CHECK_VERSION(2, 32, 0) 374*bb618362Schristos WebKitWebContext *wkctx; 375*bb618362Schristos WebKitWebsiteDataManager *wkmgr; 376*bb618362Schristos 377*bb618362Schristos wkctx = webkit_web_context_get_default(); 378*bb618362Schristos wkmgr = webkit_web_context_get_website_data_manager(wkctx); 379*bb618362Schristos webkit_website_data_manager_set_tls_errors_policy( 380*bb618362Schristos wkmgr, WEBKIT_TLS_ERRORS_POLICY_IGNORE); 381*bb618362Schristos #else 382*bb618362Schristos WebKitWebContext *wkctx; 383*bb618362Schristos 384*bb618362Schristos wkctx = webkit_web_context_get_default(); 385*bb618362Schristos webkit_web_context_set_tls_errors_policy( 386*bb618362Schristos wkctx, WEBKIT_TLS_ERRORS_POLICY_IGNORE); 387*bb618362Schristos #endif 388*bb618362Schristos } 389*bb618362Schristos #endif /* USE_WEBKIT2 */ 390*bb618362Schristos 3913c260e60Schristos webkit_web_view_load_uri(view, url); 3923c260e60Schristos 393*bb618362Schristos ctx.gtk_main_started = 1; 3943c260e60Schristos gtk_main(); 3953c260e60Schristos gtk_widget_destroy(ctx.win); 3963c260e60Schristos while (gtk_events_pending()) 3973c260e60Schristos gtk_main_iteration(); 3983c260e60Schristos 3993c260e60Schristos free(ctx.hover_link); 4003c260e60Schristos free(ctx.title); 4013c260e60Schristos return ctx.success; 4023c260e60Schristos } 403