1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.weblayer;
6 
7 import android.net.Uri;
8 import android.os.RemoteException;
9 import android.webkit.ValueCallback;
10 
11 import androidx.annotation.NonNull;
12 import androidx.annotation.Nullable;
13 
14 import org.chromium.weblayer_private.interfaces.APICallException;
15 import org.chromium.weblayer_private.interfaces.ICookieChangedCallbackClient;
16 import org.chromium.weblayer_private.interfaces.ICookieManager;
17 import org.chromium.weblayer_private.interfaces.IProfile;
18 import org.chromium.weblayer_private.interfaces.ObjectWrapper;
19 import org.chromium.weblayer_private.interfaces.StrictModeWorkaround;
20 
21 /**
22  * Manages cookies for a WebLayer profile.
23  *
24  * @since 83
25  */
26 public class CookieManager {
27     private final ICookieManager mImpl;
28 
create(IProfile profile)29     static CookieManager create(IProfile profile) {
30         try {
31             return new CookieManager(profile.getCookieManager());
32         } catch (RemoteException e) {
33             throw new APICallException(e);
34         }
35     }
36 
37     // Constructor for test mocking.
CookieManager()38     protected CookieManager() {
39         mImpl = null;
40     }
41 
CookieManager(ICookieManager impl)42     private CookieManager(ICookieManager impl) {
43         mImpl = impl;
44     }
45 
46     /**
47      * Sets a cookie for the given URL.
48      *
49      * @param uri the URI for which the cookie is to be set.
50      * @param value the cookie string, using the format of the 'Set-Cookie' HTTP response header.
51      * @param callback a callback to be executed when the cookie has been set, or on failure. Called
52      *     with true if the cookie is set successfully, and false if the cookie is not set for
53      *     security reasons.
54      *
55      * @throws IllegalArgumentException if the cookie is invalid.
56      */
setCookie( @onNull Uri uri, @NonNull String value, @Nullable Callback<Boolean> callback)57     public void setCookie(
58             @NonNull Uri uri, @NonNull String value, @Nullable Callback<Boolean> callback) {
59         ThreadCheck.ensureOnUiThread();
60         try {
61             ValueCallback<Boolean> valueCallback = (Boolean result) -> {
62                 if (callback != null) {
63                     callback.onResult(result);
64                 }
65             };
66             if (!mImpl.setCookie(uri.toString(), value, ObjectWrapper.wrap(valueCallback))) {
67                 throw new IllegalArgumentException("Invalid cookie: " + value);
68             }
69         } catch (RemoteException e) {
70             throw new APICallException(e);
71         }
72     }
73 
74     /**
75      * Gets the cookies for the given URL.
76      *
77      * @param uri the URI to get cookies for.
78      * @param callback a callback to be executed with the cookie value in the format of the 'Cookie'
79      *     HTTP request header. If there is no cookie, this will be called with an empty string.
80      */
getCookie(@onNull Uri uri, @NonNull Callback<String> callback)81     public void getCookie(@NonNull Uri uri, @NonNull Callback<String> callback) {
82         ThreadCheck.ensureOnUiThread();
83         try {
84             ValueCallback<String> valueCallback = (String result) -> {
85                 callback.onResult(result);
86             };
87             mImpl.getCookie(uri.toString(), ObjectWrapper.wrap(valueCallback));
88         } catch (RemoteException e) {
89             throw new APICallException(e);
90         }
91     }
92 
93     /**
94      * Adds a callback to listen for changes to cookies for the given URI.
95      *
96      * @param uri the URI to listen to cookie changes on.
97      * @param name the name of the cookie to listen for changes on. Can be null to listen for
98      *     changes on all cookies.
99      * @param callback a callback that will be notified on cookie changes.
100      * @return a Runnable which will unregister the callback from listening to cookie changes.
101      * @throws IllegalArgumentException if the cookie name is an empty string.
102      */
103     @NonNull
addCookieChangedCallback( @onNull Uri uri, @Nullable String name, @NonNull CookieChangedCallback callback)104     public Runnable addCookieChangedCallback(
105             @NonNull Uri uri, @Nullable String name, @NonNull CookieChangedCallback callback) {
106         ThreadCheck.ensureOnUiThread();
107         if (name != null && name.isEmpty()) {
108             throw new IllegalArgumentException(
109                     "Name cannot be empty, use null to listen for all cookie changes.");
110         }
111         try {
112             return ObjectWrapper.unwrap(mImpl.addCookieChangedCallback(uri.toString(), name,
113                                                 new CookieChangedCallbackClientImpl(callback)),
114                     Runnable.class);
115         } catch (RemoteException e) {
116             throw new APICallException(e);
117         }
118     }
119 
120     private static final class CookieChangedCallbackClientImpl
121             extends ICookieChangedCallbackClient.Stub {
122         private final CookieChangedCallback mCallback;
123 
CookieChangedCallbackClientImpl(CookieChangedCallback callback)124         CookieChangedCallbackClientImpl(CookieChangedCallback callback) {
125             mCallback = callback;
126         }
127 
128         @Override
onCookieChanged(String cookie, @CookieChangeCause int cause)129         public void onCookieChanged(String cookie, @CookieChangeCause int cause) {
130             StrictModeWorkaround.apply();
131             mCallback.onCookieChanged(cookie, cause);
132         }
133     }
134 }
135