| /* |
| * 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 <webkit2/webkit2.h> |
| #else /* USE_WEBKIT2 */ |
| #include <webkit/webkit.h> |
| #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; |
| } |