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#include <webkit/webkit.h>
11
12#include "common.h"
13#include "browser.h"
14
15
16struct browser_context {
17	GtkWidget *win;
18	int success;
19	int progress;
20	char *hover_link;
21	char *title;
22};
23
24static void win_cb_destroy(GtkWidget *win, struct browser_context *ctx)
25{
26	wpa_printf(MSG_DEBUG, "BROWSER:%s", __func__);
27	gtk_main_quit();
28}
29
30
31static void browser_update_title(struct browser_context *ctx)
32{
33	char buf[100];
34
35	if (ctx->hover_link) {
36		gtk_window_set_title(GTK_WINDOW(ctx->win), ctx->hover_link);
37		return;
38	}
39
40	if (ctx->progress == 100) {
41		gtk_window_set_title(GTK_WINDOW(ctx->win),
42				     ctx->title ? ctx->title :
43				     "Hotspot 2.0 client");
44		return;
45	}
46
47	snprintf(buf, sizeof(buf), "[%d%%] %s", ctx->progress,
48		 ctx->title ? ctx->title : "Hotspot 2.0 client");
49	gtk_window_set_title(GTK_WINDOW(ctx->win), buf);
50}
51
52
53static void view_cb_notify_progress(WebKitWebView *view, GParamSpec *pspec,
54				    struct browser_context *ctx)
55{
56	ctx->progress = 100 * webkit_web_view_get_progress(view);
57	wpa_printf(MSG_DEBUG, "BROWSER:%s progress=%d", __func__,
58		   ctx->progress);
59	browser_update_title(ctx);
60}
61
62
63static void view_cb_notify_load_status(WebKitWebView *view, GParamSpec *pspec,
64				       struct browser_context *ctx)
65{
66	int status = webkit_web_view_get_load_status(view);
67	wpa_printf(MSG_DEBUG, "BROWSER:%s load-status=%d uri=%s",
68		   __func__, status, webkit_web_view_get_uri(view));
69}
70
71
72static void view_cb_resource_request_starting(WebKitWebView *view,
73					      WebKitWebFrame *frame,
74					      WebKitWebResource *res,
75					      WebKitNetworkRequest *req,
76					      WebKitNetworkResponse *resp,
77					      struct browser_context *ctx)
78{
79	const gchar *uri = webkit_network_request_get_uri(req);
80	wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri);
81	if (g_str_has_suffix(uri, "/favicon.ico"))
82		webkit_network_request_set_uri(req, "about:blank");
83	if (g_str_has_prefix(uri, "osu://")) {
84		ctx->success = atoi(uri + 6);
85		gtk_main_quit();
86	}
87	if (g_str_has_prefix(uri, "http://localhost:12345")) {
88		/*
89		 * This is used as a special trigger to indicate that the
90		 * user exchange has been completed.
91		 */
92		ctx->success = 1;
93		gtk_main_quit();
94	}
95}
96
97
98static gboolean view_cb_mime_type_policy_decision(
99	WebKitWebView *view, WebKitWebFrame *frame, WebKitNetworkRequest *req,
100	gchar *mime, WebKitWebPolicyDecision *policy,
101	struct browser_context *ctx)
102{
103	wpa_printf(MSG_DEBUG, "BROWSER:%s mime=%s", __func__, mime);
104
105	if (!webkit_web_view_can_show_mime_type(view, mime)) {
106		webkit_web_policy_decision_download(policy);
107		return TRUE;
108	}
109
110	return FALSE;
111}
112
113
114static gboolean view_cb_download_requested(WebKitWebView *view,
115					   WebKitDownload *dl,
116					   struct browser_context *ctx)
117{
118	const gchar *uri;
119	uri = webkit_download_get_uri(dl);
120	wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri);
121	return FALSE;
122}
123
124
125static void view_cb_hovering_over_link(WebKitWebView *view, gchar *title,
126				       gchar *uri, struct browser_context *ctx)
127{
128	wpa_printf(MSG_DEBUG, "BROWSER:%s title=%s uri=%s", __func__, title,
129		   uri);
130	os_free(ctx->hover_link);
131	if (uri)
132		ctx->hover_link = os_strdup(uri);
133	else
134		ctx->hover_link = NULL;
135
136	browser_update_title(ctx);
137}
138
139
140static void view_cb_title_changed(WebKitWebView *view, WebKitWebFrame *frame,
141				  const char *title,
142				  struct browser_context *ctx)
143{
144	wpa_printf(MSG_DEBUG, "BROWSER:%s title=%s", __func__, title);
145	os_free(ctx->title);
146	ctx->title = os_strdup(title);
147	browser_update_title(ctx);
148}
149
150
151int hs20_web_browser(const char *url)
152{
153	GtkWidget *scroll;
154	SoupSession *s;
155	WebKitWebView *view;
156	WebKitWebSettings *settings;
157	struct browser_context ctx;
158
159	memset(&ctx, 0, sizeof(ctx));
160	if (!gtk_init_check(NULL, NULL))
161		return -1;
162
163	s = webkit_get_default_session();
164	g_object_set(G_OBJECT(s), "ssl-ca-file",
165		     "/etc/ssl/certs/ca-certificates.crt", NULL);
166	g_object_set(G_OBJECT(s), "ssl-strict", FALSE, NULL);
167
168	ctx.win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
169	gtk_window_set_wmclass(GTK_WINDOW(ctx.win), "Hotspot 2.0 client",
170			       "Hotspot 2.0 client");
171	gtk_window_set_default_size(GTK_WINDOW(ctx.win), 800, 600);
172
173	scroll = gtk_scrolled_window_new(NULL, NULL);
174	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
175				       GTK_POLICY_NEVER, GTK_POLICY_NEVER);
176
177	g_signal_connect(G_OBJECT(ctx.win), "destroy",
178			 G_CALLBACK(win_cb_destroy), &ctx);
179
180	view = WEBKIT_WEB_VIEW(webkit_web_view_new());
181	g_signal_connect(G_OBJECT(view), "notify::progress",
182			 G_CALLBACK(view_cb_notify_progress), &ctx);
183	g_signal_connect(G_OBJECT(view), "notify::load-status",
184			 G_CALLBACK(view_cb_notify_load_status), &ctx);
185	g_signal_connect(G_OBJECT(view), "resource-request-starting",
186			 G_CALLBACK(view_cb_resource_request_starting), &ctx);
187	g_signal_connect(G_OBJECT(view), "mime-type-policy-decision-requested",
188			 G_CALLBACK(view_cb_mime_type_policy_decision), &ctx);
189	g_signal_connect(G_OBJECT(view), "download-requested",
190			 G_CALLBACK(view_cb_download_requested), &ctx);
191	g_signal_connect(G_OBJECT(view), "hovering-over-link",
192			 G_CALLBACK(view_cb_hovering_over_link), &ctx);
193	g_signal_connect(G_OBJECT(view), "title-changed",
194			 G_CALLBACK(view_cb_title_changed), &ctx);
195
196	gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(view));
197	gtk_container_add(GTK_CONTAINER(ctx.win), GTK_WIDGET(scroll));
198
199	gtk_widget_grab_focus(GTK_WIDGET(view));
200	gtk_widget_show_all(ctx.win);
201
202	settings = webkit_web_view_get_settings(view);
203	g_object_set(G_OBJECT(settings), "user-agent",
204		     "Mozilla/5.0 (X11; U; Unix; en-US) "
205		     "AppleWebKit/537.15 (KHTML, like Gecko) "
206		     "hs20-client/1.0", NULL);
207	g_object_set(G_OBJECT(settings), "auto-load-images", TRUE, NULL);
208
209	webkit_web_view_load_uri(view, url);
210
211	gtk_main();
212	gtk_widget_destroy(ctx.win);
213	while (gtk_events_pending())
214		gtk_main_iteration();
215
216	free(ctx.hover_link);
217	free(ctx.title);
218	return ctx.success;
219}
220