1/*
2 * Copyright (C) 2012 Igalia S.L.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2,1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB.  If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "WebKitCookieManager.h"
22
23#include "SoupCookiePersistentStorageType.h"
24#include "WebCookieManagerProxy.h"
25#include "WebKitCookieManagerPrivate.h"
26#include "WebKitEnumTypes.h"
27#include <wtf/gobject/GRefPtr.h>
28#include <wtf/text/CString.h>
29
30using namespace WebKit;
31
32/**
33 * SECTION: WebKitCookieManager
34 * @Short_description: Defines how to handle cookies in a #WebKitWebContext
35 * @Title: WebKitCookieManager
36 *
37 * The #WebKitCookieManager defines how to handle cookies in a
38 * #WebKitWebContext. Get it from the context with
39 * webkit_web_context_get_cookie_manager(), and use it to set where to
40 * store cookies, with webkit_cookie_manager_set_persistent_storage(),
41 * to get the list of domains with cookies, with
42 * webkit_cookie_manager_get_domains_with_cookies(), or to set the
43 * acceptance policy, with webkit_cookie_manager_get_accept_policy()
44 * (among other actions).
45 *
46 */
47
48enum {
49    CHANGED,
50
51    LAST_SIGNAL
52};
53
54struct _WebKitCookieManagerPrivate {
55    ~_WebKitCookieManagerPrivate()
56    {
57        webCookieManager->stopObservingCookieChanges();
58    }
59
60    RefPtr<WebCookieManagerProxy> webCookieManager;
61};
62
63static guint signals[LAST_SIGNAL] = { 0, };
64
65WEBKIT_DEFINE_TYPE(WebKitCookieManager, webkit_cookie_manager, G_TYPE_OBJECT)
66
67COMPILE_ASSERT_MATCHING_ENUM(WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT, SoupCookiePersistentStorageText);
68COMPILE_ASSERT_MATCHING_ENUM(WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE, SoupCookiePersistentStorageSQLite);
69
70COMPILE_ASSERT_MATCHING_ENUM(WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS, HTTPCookieAcceptPolicyAlways);
71COMPILE_ASSERT_MATCHING_ENUM(WEBKIT_COOKIE_POLICY_ACCEPT_NEVER, HTTPCookieAcceptPolicyNever);
72COMPILE_ASSERT_MATCHING_ENUM(WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY, HTTPCookieAcceptPolicyOnlyFromMainDocumentDomain);
73
74static void webkit_cookie_manager_class_init(WebKitCookieManagerClass* findClass)
75{
76    GObjectClass* gObjectClass = G_OBJECT_CLASS(findClass);
77
78    /**
79     * WebKitCookieManager::changed:
80     * @cookie_manager: the #WebKitCookieManager
81     *
82     * This signal is emitted when cookies are added, removed or modified.
83     */
84    signals[CHANGED] =
85        g_signal_new("changed",
86                     G_TYPE_FROM_CLASS(gObjectClass),
87                     G_SIGNAL_RUN_LAST,
88                     0, 0, 0,
89                     g_cclosure_marshal_VOID__VOID,
90                     G_TYPE_NONE, 0);
91}
92
93static void cookiesDidChange(WKCookieManagerRef, const void* clientInfo)
94{
95    g_signal_emit(WEBKIT_COOKIE_MANAGER(clientInfo), signals[CHANGED], 0);
96}
97
98WebKitCookieManager* webkitCookieManagerCreate(WebCookieManagerProxy* webCookieManager)
99{
100    WebKitCookieManager* manager = WEBKIT_COOKIE_MANAGER(g_object_new(WEBKIT_TYPE_COOKIE_MANAGER, NULL));
101    manager->priv->webCookieManager = webCookieManager;
102
103    WKCookieManagerClient wkCookieManagerClient = {
104        kWKCookieManagerClientCurrentVersion,
105        manager, // clientInfo
106        cookiesDidChange
107    };
108    WKCookieManagerSetClient(toAPI(webCookieManager), &wkCookieManagerClient);
109    manager->priv->webCookieManager->startObservingCookieChanges();
110
111    return manager;
112}
113
114/**
115 * webkit_cookie_manager_set_persistent_storage:
116 * @cookie_manager: a #WebKitCookieManager
117 * @filename: the filename to read to/write from
118 * @storage: a #WebKitCookiePersistentStorage
119 *
120 * Set the @filename where non-session cookies are stored persistently using
121 * @storage as the format to read/write the cookies.
122 * Cookies are initially read from @filename to create an initial set of cookies.
123 * Then, non-session cookies will be written to @filename when the WebKitCookieManager::changed
124 * signal is emitted.
125 * By default, @cookie_manager doesn't store the cookies persistenly, so you need to call this
126 * method to keep cookies saved across sessions.
127 */
128void webkit_cookie_manager_set_persistent_storage(WebKitCookieManager* manager, const char* filename, WebKitCookiePersistentStorage storage)
129{
130    g_return_if_fail(WEBKIT_IS_COOKIE_MANAGER(manager));
131    g_return_if_fail(filename);
132
133    manager->priv->webCookieManager->stopObservingCookieChanges();
134    manager->priv->webCookieManager->setCookiePersistentStorage(String::fromUTF8(filename), storage);
135    manager->priv->webCookieManager->startObservingCookieChanges();
136}
137
138/**
139 * webkit_cookie_manager_set_accept_policy:
140 * @cookie_manager: a #WebKitCookieManager
141 * @policy: a #WebKitCookieAcceptPolicy
142 *
143 * Set the cookie acceptance policy of @cookie_manager as @policy.
144 */
145void webkit_cookie_manager_set_accept_policy(WebKitCookieManager* manager, WebKitCookieAcceptPolicy policy)
146{
147    g_return_if_fail(WEBKIT_IS_COOKIE_MANAGER(manager));
148
149    manager->priv->webCookieManager->setHTTPCookieAcceptPolicy(policy);
150}
151
152struct GetAcceptPolicyAsyncData {
153    WKHTTPCookieAcceptPolicy policy;
154    GRefPtr<GCancellable> cancellable;
155};
156WEBKIT_DEFINE_ASYNC_DATA_STRUCT(GetAcceptPolicyAsyncData)
157
158static void webkitCookieManagerGetAcceptPolicyCallback(WKHTTPCookieAcceptPolicy policy, WKErrorRef, void* context)
159{
160    GRefPtr<GSimpleAsyncResult> result = adoptGRef(G_SIMPLE_ASYNC_RESULT(context));
161    GetAcceptPolicyAsyncData* data = static_cast<GetAcceptPolicyAsyncData*>(g_simple_async_result_get_op_res_gpointer(result.get()));
162    GError* error = 0;
163    if (g_cancellable_set_error_if_cancelled(data->cancellable.get(), &error))
164        g_simple_async_result_take_error(result.get(), error);
165    else
166        data->policy = policy;
167    g_simple_async_result_complete(result.get());
168}
169
170/**
171 * webkit_cookie_manager_get_accept_policy:
172 * @cookie_manager: a #WebKitCookieManager
173 * @cancellable: (allow-none): a #GCancellable or %NULL to ignore
174 * @callback: (scope async): a #GAsyncReadyCallback to call when the request is satisfied
175 * @user_data: (closure): the data to pass to callback function
176 *
177 * Asynchronously get the cookie acceptance policy of @cookie_manager.
178 *
179 * When the operation is finished, @callback will be called. You can then call
180 * webkit_cookie_manager_get_accept_policy_finish() to get the result of the operation.
181 */
182void webkit_cookie_manager_get_accept_policy(WebKitCookieManager* manager, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData)
183{
184    g_return_if_fail(WEBKIT_IS_COOKIE_MANAGER(manager));
185
186    GSimpleAsyncResult* result = g_simple_async_result_new(G_OBJECT(manager), callback, userData,
187                                                           reinterpret_cast<gpointer>(webkit_cookie_manager_get_accept_policy));
188    GetAcceptPolicyAsyncData* data = createGetAcceptPolicyAsyncData();
189    data->cancellable = cancellable;
190    g_simple_async_result_set_op_res_gpointer(result, data, reinterpret_cast<GDestroyNotify>(destroyGetAcceptPolicyAsyncData));
191
192    manager->priv->webCookieManager->getHTTPCookieAcceptPolicy(HTTPCookieAcceptPolicyCallback::create(result, webkitCookieManagerGetAcceptPolicyCallback));
193}
194
195/**
196 * webkit_cookie_manager_get_accept_policy_finish:
197 * @cookie_manager: a #WebKitCookieManager
198 * @result: a #GAsyncResult
199 * @error: return location for error or %NULL to ignore
200 *
201 * Finish an asynchronous operation started with webkit_cookie_manager_get_accept_policy().
202 *
203 * Returns: the cookie acceptance policy of @cookie_manager as a #WebKitCookieAcceptPolicy.
204 */
205WebKitCookieAcceptPolicy webkit_cookie_manager_get_accept_policy_finish(WebKitCookieManager* manager, GAsyncResult* result, GError** error)
206{
207    g_return_val_if_fail(WEBKIT_IS_COOKIE_MANAGER(manager), WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY);
208    g_return_val_if_fail(G_IS_ASYNC_RESULT(result), WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY);
209
210    GSimpleAsyncResult* simpleResult = G_SIMPLE_ASYNC_RESULT(result);
211    g_warn_if_fail(g_simple_async_result_get_source_tag(simpleResult) == webkit_cookie_manager_get_accept_policy);
212
213    if (g_simple_async_result_propagate_error(simpleResult, error))
214        return WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY;
215
216    GetAcceptPolicyAsyncData* data = static_cast<GetAcceptPolicyAsyncData*>(g_simple_async_result_get_op_res_gpointer(simpleResult));
217    return static_cast<WebKitCookieAcceptPolicy>(data->policy);
218}
219
220struct GetDomainsWithCookiesAsyncData {
221    GRefPtr<GPtrArray> domains;
222    GRefPtr<GCancellable> cancellable;
223};
224WEBKIT_DEFINE_ASYNC_DATA_STRUCT(GetDomainsWithCookiesAsyncData)
225
226static void webkitCookieManagerGetDomainsWithCookiesCallback(WKArrayRef wkDomains, WKErrorRef, void* context)
227{
228    GRefPtr<GSimpleAsyncResult> result = adoptGRef(G_SIMPLE_ASYNC_RESULT(context));
229    GetDomainsWithCookiesAsyncData* data = static_cast<GetDomainsWithCookiesAsyncData*>(g_simple_async_result_get_op_res_gpointer(result.get()));
230    GError* error = 0;
231    if (g_cancellable_set_error_if_cancelled(data->cancellable.get(), &error))
232        g_simple_async_result_take_error(result.get(), error);
233    else {
234        ImmutableArray* domains = toImpl(wkDomains);
235        data->domains = adoptGRef(g_ptr_array_new_with_free_func(g_free));
236        for (size_t i = 0; i < domains->size(); ++i) {
237            WebString* domainString = static_cast<WebString*>(domains->at(i));
238            String domain = domainString->string();
239            if (domain.isEmpty())
240                continue;
241            g_ptr_array_add(data->domains.get(), g_strdup(domain.utf8().data()));
242        }
243        g_ptr_array_add(data->domains.get(), 0);
244    }
245    g_simple_async_result_complete(result.get());
246}
247
248/**
249 * webkit_cookie_manager_get_domains_with_cookies:
250 * @cookie_manager: a #WebKitCookieManager
251 * @cancellable: (allow-none): a #GCancellable or %NULL to ignore
252 * @callback: (scope async): a #GAsyncReadyCallback to call when the request is satisfied
253 * @user_data: (closure): the data to pass to callback function
254 *
255 * Asynchronously get the list of domains for which @cookie_manager contains cookies.
256 *
257 * When the operation is finished, @callback will be called. You can then call
258 * webkit_cookie_manager_get_domains_with_cookies_finish() to get the result of the operation.
259 */
260void webkit_cookie_manager_get_domains_with_cookies(WebKitCookieManager* manager, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData)
261{
262    g_return_if_fail(WEBKIT_IS_COOKIE_MANAGER(manager));
263
264    GSimpleAsyncResult* result = g_simple_async_result_new(G_OBJECT(manager), callback, userData,
265                                                           reinterpret_cast<gpointer>(webkit_cookie_manager_get_domains_with_cookies));
266    GetDomainsWithCookiesAsyncData* data = createGetDomainsWithCookiesAsyncData();
267    data->cancellable = cancellable;
268    g_simple_async_result_set_op_res_gpointer(result, data, reinterpret_cast<GDestroyNotify>(destroyGetDomainsWithCookiesAsyncData));
269    manager->priv->webCookieManager->getHostnamesWithCookies(ArrayCallback::create(result, webkitCookieManagerGetDomainsWithCookiesCallback));
270}
271
272/**
273 * webkit_cookie_manager_get_domains_with_cookies_finish:
274 * @cookie_manager: a #WebKitCookieManager
275 * @result: a #GAsyncResult
276 * @error: return location for error or %NULL to ignore
277 *
278 * Finish an asynchronous operation started with webkit_cookie_manager_get_domains_with_cookies().
279 * The return value is a %NULL terminated list of strings which should
280 * be released with g_strfreev().
281 *
282 * Returns: (transfer full) (array zero-terminated=1): A %NULL terminated array of domain names
283 *    or %NULL in case of error.
284 */
285gchar** webkit_cookie_manager_get_domains_with_cookies_finish(WebKitCookieManager* manager, GAsyncResult* result, GError** error)
286{
287    g_return_val_if_fail(WEBKIT_IS_COOKIE_MANAGER(manager), 0);
288    g_return_val_if_fail(G_IS_ASYNC_RESULT(result), 0);
289
290    GSimpleAsyncResult* simpleResult = G_SIMPLE_ASYNC_RESULT(result);
291    g_warn_if_fail(g_simple_async_result_get_source_tag(simpleResult) == webkit_cookie_manager_get_domains_with_cookies);
292
293    if (g_simple_async_result_propagate_error(simpleResult, error))
294        return 0;
295
296    GetDomainsWithCookiesAsyncData* data = static_cast<GetDomainsWithCookiesAsyncData*>(g_simple_async_result_get_op_res_gpointer(simpleResult));
297    return reinterpret_cast<char**>(g_ptr_array_free(data->domains.leakRef(), FALSE));
298}
299
300/**
301 * webkit_cookie_manager_delete_cookies_for_domain:
302 * @cookie_manager: a #WebKitCookieManager
303 * @domain: a domain name
304 *
305 * Remove all cookies of @cookie_manager for the given @domain.
306 */
307void webkit_cookie_manager_delete_cookies_for_domain(WebKitCookieManager* manager, const gchar* domain)
308{
309    g_return_if_fail(WEBKIT_IS_COOKIE_MANAGER(manager));
310    g_return_if_fail(domain);
311
312    manager->priv->webCookieManager->deleteCookiesForHostname(String::fromUTF8(domain));
313}
314
315/**
316 * webkit_cookie_manager_delete_all_cookies:
317 * @cookie_manager: a #WebKitCookieManager
318 *
319 * Delete all cookies of @cookie_manager
320 */
321void webkit_cookie_manager_delete_all_cookies(WebKitCookieManager* manager)
322{
323    g_return_if_fail(WEBKIT_IS_COOKIE_MANAGER(manager));
324
325    manager->priv->webCookieManager->deleteAllCookies();
326}
327