/* * Hotspot 2.0 client - Web browser using WebKit * Copyright (c) 2013, Qualcomm Atheros, Inc. * * This software may be distributed under the terms of the BSD license. * See README for more details. */ #include "includes.h" #ifdef USE_WEBKIT2 #include #else /* USE_WEBKIT2 */ #include #endif /* USE_WEBKIT2 */ #include "common.h" #include "browser.h" struct browser_context { GtkWidget *win; WebKitWebView *view; int success; int progress; char *hover_link; char *title; int gtk_main_started; int quit_gtk_main; }; static void win_cb_destroy(GtkWidget *win, struct browser_context *ctx) { wpa_printf(MSG_DEBUG, "BROWSER:%s", __func__); if (ctx->gtk_main_started) gtk_main_quit(); } static void browser_update_title(struct browser_context *ctx) { char buf[100]; if (ctx->hover_link) { gtk_window_set_title(GTK_WINDOW(ctx->win), ctx->hover_link); return; } if (ctx->progress == 100) { gtk_window_set_title(GTK_WINDOW(ctx->win), ctx->title ? ctx->title : "Hotspot 2.0 client"); return; } snprintf(buf, sizeof(buf), "[%d%%] %s", ctx->progress, ctx->title ? ctx->title : "Hotspot 2.0 client"); gtk_window_set_title(GTK_WINDOW(ctx->win), buf); } static void process_request_starting_uri(struct browser_context *ctx, const char *uri) { int quit = 0; if (g_str_has_prefix(uri, "osu://")) { ctx->success = atoi(uri + 6); quit = 1; } else if (g_str_has_prefix(uri, "http://localhost:12345")) { /* * This is used as a special trigger to indicate that the * user exchange has been completed. */ ctx->success = 1; quit = 1; } if (quit) { if (ctx->gtk_main_started) { gtk_main_quit(); ctx->gtk_main_started = 0; } else { ctx->quit_gtk_main = 1; } } } #ifdef USE_WEBKIT2 static void view_cb_notify_estimated_load_progress(WebKitWebView *view, GParamSpec *pspec, struct browser_context *ctx) { ctx->progress = 100 * webkit_web_view_get_estimated_load_progress(view); wpa_printf(MSG_DEBUG, "BROWSER:%s progress=%d", __func__, ctx->progress); browser_update_title(ctx); } static void view_cb_resource_load_starting(WebKitWebView *view, WebKitWebResource *res, WebKitURIRequest *req, struct browser_context *ctx) { const gchar *uri = webkit_uri_request_get_uri(req); wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri); process_request_starting_uri(ctx, uri); } static gboolean view_cb_decide_policy(WebKitWebView *view, WebKitPolicyDecision *policy, WebKitPolicyDecisionType type, struct browser_context *ctx) { wpa_printf(MSG_DEBUG, "BROWSER:%s type=%d", __func__, type); switch (type) { case WEBKIT_POLICY_DECISION_TYPE_RESPONSE: { /* This function makes webkit send a download signal for all * unknown mime types. */ WebKitResponsePolicyDecision *response; response = WEBKIT_RESPONSE_POLICY_DECISION(policy); if (!webkit_response_policy_decision_is_mime_type_supported( response)) { webkit_policy_decision_download(policy); return TRUE; } break; } case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION: { WebKitNavigationPolicyDecision *d; WebKitNavigationAction *a; WebKitURIRequest *req; const gchar *uri; d = WEBKIT_NAVIGATION_POLICY_DECISION(policy); a = webkit_navigation_policy_decision_get_navigation_action(d); req = webkit_navigation_action_get_request(a); uri = webkit_uri_request_get_uri(req); wpa_printf(MSG_DEBUG, "BROWSER:%s navigation action: uri=%s", __func__, uri); process_request_starting_uri(ctx, uri); break; } default: break; } return FALSE; } static void view_cb_mouse_target_changed(WebKitWebView *view, WebKitHitTestResult *h, guint modifiers, struct browser_context *ctx) { WebKitHitTestResultContext hc = webkit_hit_test_result_get_context(h); const char *uri = NULL; if (hc & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) uri = webkit_hit_test_result_get_link_uri(h); else if (hc & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) uri = webkit_hit_test_result_get_image_uri(h); else if (hc & WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA) uri = webkit_hit_test_result_get_media_uri(h); wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri ? uri : "N/A"); os_free(ctx->hover_link); if (uri) ctx->hover_link = os_strdup(uri); else ctx->hover_link = NULL; browser_update_title(ctx); } static void view_cb_notify_title(WebKitWebView *view, GParamSpec *ps, struct browser_context *ctx) { const char *title; title = webkit_web_view_get_title(ctx->view); wpa_printf(MSG_DEBUG, "BROWSER:%s title=%s", __func__, title); os_free(ctx->title); ctx->title = os_strdup(title); browser_update_title(ctx); } #else /* USE_WEBKIT2 */ static void view_cb_notify_progress(WebKitWebView *view, GParamSpec *pspec, struct browser_context *ctx) { ctx->progress = 100 * webkit_web_view_get_progress(view); wpa_printf(MSG_DEBUG, "BROWSER:%s progress=%d", __func__, ctx->progress); browser_update_title(ctx); } static void view_cb_notify_load_status(WebKitWebView *view, GParamSpec *pspec, struct browser_context *ctx) { int status = webkit_web_view_get_load_status(view); wpa_printf(MSG_DEBUG, "BROWSER:%s load-status=%d uri=%s", __func__, status, webkit_web_view_get_uri(view)); if (ctx->quit_gtk_main) { gtk_main_quit(); ctx->gtk_main_started = 0; } } static void view_cb_resource_request_starting(WebKitWebView *view, WebKitWebFrame *frame, WebKitWebResource *res, WebKitNetworkRequest *req, WebKitNetworkResponse *resp, struct browser_context *ctx) { const gchar *uri = webkit_network_request_get_uri(req); wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri); if (g_str_has_suffix(uri, "/favicon.ico")) webkit_network_request_set_uri(req, "about:blank"); process_request_starting_uri(ctx, uri); } static gboolean view_cb_mime_type_policy_decision( WebKitWebView *view, WebKitWebFrame *frame, WebKitNetworkRequest *req, gchar *mime, WebKitWebPolicyDecision *policy, struct browser_context *ctx) { wpa_printf(MSG_DEBUG, "BROWSER:%s mime=%s", __func__, mime); if (!webkit_web_view_can_show_mime_type(view, mime)) { webkit_web_policy_decision_download(policy); return TRUE; } return FALSE; } static gboolean view_cb_download_requested(WebKitWebView *view, WebKitDownload *dl, struct browser_context *ctx) { const gchar *uri; uri = webkit_download_get_uri(dl); wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri); return FALSE; } static void view_cb_hovering_over_link(WebKitWebView *view, gchar *title, gchar *uri, struct browser_context *ctx) { wpa_printf(MSG_DEBUG, "BROWSER:%s title=%s uri=%s", __func__, title, uri); os_free(ctx->hover_link); if (uri) ctx->hover_link = os_strdup(uri); else ctx->hover_link = NULL; browser_update_title(ctx); } static void view_cb_title_changed(WebKitWebView *view, WebKitWebFrame *frame, const char *title, struct browser_context *ctx) { wpa_printf(MSG_DEBUG, "BROWSER:%s title=%s", __func__, title); os_free(ctx->title); ctx->title = os_strdup(title); browser_update_title(ctx); } #endif /* USE_WEBKIT2 */ int hs20_web_browser(const char *url, int ignore_tls) { GtkWidget *scroll; WebKitWebView *view; #ifdef USE_WEBKIT2 WebKitSettings *settings; #else /* USE_WEBKIT2 */ WebKitWebSettings *settings; SoupSession *s; #endif /* USE_WEBKIT2 */ struct browser_context ctx; memset(&ctx, 0, sizeof(ctx)); if (!gtk_init_check(NULL, NULL)) return -1; #ifndef USE_WEBKIT2 s = webkit_get_default_session(); g_object_set(G_OBJECT(s), "ssl-ca-file", "/etc/ssl/certs/ca-certificates.crt", NULL); if (ignore_tls) g_object_set(G_OBJECT(s), "ssl-strict", FALSE, NULL); #endif /* USE_WEBKIT2 */ ctx.win = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_role(GTK_WINDOW(ctx.win), "Hotspot 2.0 client"); gtk_window_set_default_size(GTK_WINDOW(ctx.win), 800, 600); scroll = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_NEVER, GTK_POLICY_NEVER); g_signal_connect(G_OBJECT(ctx.win), "destroy", G_CALLBACK(win_cb_destroy), &ctx); view = WEBKIT_WEB_VIEW(webkit_web_view_new()); ctx.view = view; #ifdef USE_WEBKIT2 g_signal_connect(G_OBJECT(view), "notify::estimated-load-progress", G_CALLBACK(view_cb_notify_estimated_load_progress), &ctx); g_signal_connect(G_OBJECT(view), "resource-load-started", G_CALLBACK(view_cb_resource_load_starting), &ctx); g_signal_connect(G_OBJECT(view), "decide-policy", G_CALLBACK(view_cb_decide_policy), &ctx); g_signal_connect(G_OBJECT(view), "mouse-target-changed", G_CALLBACK(view_cb_mouse_target_changed), &ctx); g_signal_connect(G_OBJECT(view), "notify::title", G_CALLBACK(view_cb_notify_title), &ctx); #else /* USE_WEBKIT2 */ g_signal_connect(G_OBJECT(view), "notify::load-status", G_CALLBACK(view_cb_notify_load_status), &ctx); g_signal_connect(G_OBJECT(view), "notify::progress", G_CALLBACK(view_cb_notify_progress), &ctx); g_signal_connect(G_OBJECT(view), "resource-request-starting", G_CALLBACK(view_cb_resource_request_starting), &ctx); g_signal_connect(G_OBJECT(view), "mime-type-policy-decision-requested", G_CALLBACK(view_cb_mime_type_policy_decision), &ctx); g_signal_connect(G_OBJECT(view), "download-requested", G_CALLBACK(view_cb_download_requested), &ctx); g_signal_connect(G_OBJECT(view), "hovering-over-link", G_CALLBACK(view_cb_hovering_over_link), &ctx); g_signal_connect(G_OBJECT(view), "title-changed", G_CALLBACK(view_cb_title_changed), &ctx); #endif /* USE_WEBKIT2 */ gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(ctx.win), GTK_WIDGET(scroll)); gtk_widget_grab_focus(GTK_WIDGET(view)); gtk_widget_show_all(ctx.win); settings = webkit_web_view_get_settings(view); g_object_set(G_OBJECT(settings), "user-agent", "Mozilla/5.0 (X11; U; Unix; en-US) " "AppleWebKit/537.15 (KHTML, like Gecko) " "hs20-client/1.0", NULL); g_object_set(G_OBJECT(settings), "auto-load-images", TRUE, NULL); #ifdef USE_WEBKIT2 if (ignore_tls) { #if WEBKIT_CHECK_VERSION(2, 32, 0) WebKitWebContext *wkctx; WebKitWebsiteDataManager *wkmgr; wkctx = webkit_web_context_get_default(); wkmgr = webkit_web_context_get_website_data_manager(wkctx); webkit_website_data_manager_set_tls_errors_policy( wkmgr, WEBKIT_TLS_ERRORS_POLICY_IGNORE); #else WebKitWebContext *wkctx; wkctx = webkit_web_context_get_default(); webkit_web_context_set_tls_errors_policy( wkctx, WEBKIT_TLS_ERRORS_POLICY_IGNORE); #endif } #endif /* USE_WEBKIT2 */ webkit_web_view_load_uri(view, url); ctx.gtk_main_started = 1; gtk_main(); gtk_widget_destroy(ctx.win); while (gtk_events_pending()) gtk_main_iteration(); free(ctx.hover_link); free(ctx.title); return ctx.success; }