|  | /* | 
|  | * 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; | 
|  | } |