• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package android.webkit;
2 
3 import java.io.UnsupportedEncodingException;
4 import java.net.MalformedURLException;
5 import java.net.URL;
6 import java.net.URLDecoder;
7 import java.text.DateFormat;
8 import java.text.ParseException;
9 import java.text.SimpleDateFormat;
10 import java.util.ArrayList;
11 import java.util.Date;
12 import java.util.List;
13 import java.util.Objects;
14 import javax.annotation.Nullable;
15 
16 /**
17  * Robolectric implementation of {@link android.webkit.CookieManager}.
18  *
19  * <p>Basic implementation which does not fully implement RFC2109.
20  */
21 public class RoboCookieManager extends CookieManager {
22   private static final String HTTP = "http://";
23   private static final String HTTPS = "https://";
24   private static final String EXPIRATION_FIELD_NAME = "Expires";
25   private static final String SECURE_ATTR_NAME = "SECURE";
26   private final List<Cookie> store = new ArrayList<>();
27   private boolean accept;
28 
29   @Override
setCookie(String url, String value)30   public void setCookie(String url, String value) {
31     Cookie cookie = parseCookie(url, value);
32     if (cookie != null) {
33       Cookie existingCookie = null;
34       for (Cookie c : store) {
35         if (c == null) {
36           continue;
37         }
38         if (Objects.equals(c.getName(), cookie.getName())
39             && Objects.equals(c.mHostname, cookie.mHostname)) {
40           existingCookie = c;
41           break;
42         }
43       }
44       if (existingCookie != null) {
45         store.remove(existingCookie);
46       }
47       store.add(cookie);
48     }
49   }
50 
51   @Override
setCookie(String url, String value, @Nullable ValueCallback<Boolean> valueCallback)52   public void setCookie(String url, String value, @Nullable ValueCallback<Boolean> valueCallback) {
53     setCookie(url, value);
54     if (valueCallback != null) {
55       valueCallback.onReceiveValue(true);
56     }
57   }
58 
59   @Override
setAcceptThirdPartyCookies(WebView webView, boolean b)60   public void setAcceptThirdPartyCookies(WebView webView, boolean b) {}
61 
62   @Override
acceptThirdPartyCookies(WebView webView)63   public boolean acceptThirdPartyCookies(WebView webView) {
64     return false;
65   }
66 
67   @Override
removeAllCookies(@ullable ValueCallback<Boolean> valueCallback)68   public void removeAllCookies(@Nullable ValueCallback<Boolean> valueCallback) {
69     store.clear();
70     if (valueCallback != null) {
71       valueCallback.onReceiveValue(Boolean.TRUE);
72     }
73   }
74 
75   @Override
flush()76   public void flush() {}
77 
78   @Override
removeSessionCookies(@ullable ValueCallback<Boolean> valueCallback)79   public void removeSessionCookies(@Nullable ValueCallback<Boolean> valueCallback) {
80     boolean value;
81     synchronized (store) {
82       value = clearAndAddPersistentCookies();
83     }
84     if (valueCallback != null) {
85       valueCallback.onReceiveValue(value);
86     }
87   }
88 
89   @Override
getCookie(String url)90   public String getCookie(String url) {
91     // Return null value for empty url
92     if (url == null || url.equals("")) {
93       return null;
94     }
95 
96     try {
97       url = URLDecoder.decode(url, "UTF-8");
98     } catch (UnsupportedEncodingException e) {
99       throw new RuntimeException(e);
100     }
101 
102     final List<Cookie> matchedCookies;
103     if (url.startsWith(".")) {
104       matchedCookies = filter(url.substring(1));
105     } else if (url.contains("//.")) {
106       matchedCookies = filter(url.substring(url.indexOf("//.") + 3));
107     } else {
108       matchedCookies = filter(getCookieHost(url), url.startsWith(HTTPS));
109     }
110     if (matchedCookies.isEmpty()) {
111       return null;
112     }
113 
114     StringBuilder cookieHeaderValue = new StringBuilder();
115     for (int i = 0, n = matchedCookies.size(); i < n; i++) {
116       Cookie cookie = matchedCookies.get(i);
117 
118       if (i > 0) {
119         cookieHeaderValue.append("; ");
120       }
121       cookieHeaderValue.append(cookie.getName());
122       String value = cookie.getValue();
123       if (value != null) {
124         cookieHeaderValue.append("=");
125         cookieHeaderValue.append(value);
126       }
127     }
128 
129     return cookieHeaderValue.toString();
130   }
131 
132   @Override
getCookie(String s, boolean b)133   public String getCookie(String s, boolean b) {
134     return null;
135   }
136 
filter(String domain)137   private List<Cookie> filter(String domain) {
138     return filter(domain, false);
139   }
140 
filter(String domain, boolean isSecure)141   private List<Cookie> filter(String domain, boolean isSecure) {
142     List<Cookie> matchedCookies = new ArrayList<>();
143     for (Cookie cookie : store) {
144       if (cookie.isSameHost(domain) && (isSecure == cookie.isSecure() || isSecure)) {
145         matchedCookies.add(cookie);
146       }
147     }
148     return matchedCookies;
149   }
150 
151   @Override
setAcceptCookie(boolean accept)152   public void setAcceptCookie(boolean accept) {
153     this.accept = accept;
154   }
155 
156   @Override
acceptCookie()157   public boolean acceptCookie() {
158     return this.accept;
159   }
160 
removeAllCookie()161   public void removeAllCookie() {
162     store.clear();
163   }
164 
removeExpiredCookie()165   public void removeExpiredCookie() {
166     List<Cookie> expired = new ArrayList<>();
167     Date now = new Date();
168 
169     for (Cookie cookie : store) {
170       if (cookie.isExpiredAt(now)) {
171         expired.add(cookie);
172       }
173     }
174 
175     store.removeAll(expired);
176   }
177 
178   @Override
hasCookies()179   public boolean hasCookies() {
180     return !store.isEmpty();
181   }
182 
183   @Override
hasCookies(boolean b)184   public boolean hasCookies(boolean b) {
185     return false;
186   }
187 
removeSessionCookie()188   public void removeSessionCookie() {
189     synchronized (store) {
190       clearAndAddPersistentCookies();
191     }
192   }
193 
194   @Override
allowFileSchemeCookiesImpl()195   protected boolean allowFileSchemeCookiesImpl() {
196     return false;
197   }
198 
199   @Override
setAcceptFileSchemeCookiesImpl(boolean b)200   protected void setAcceptFileSchemeCookiesImpl(boolean b) {}
201 
clearAndAddPersistentCookies()202   private boolean clearAndAddPersistentCookies() {
203     List<Cookie> existing = new ArrayList<>(store);
204     int length = store.size();
205     store.clear();
206     for (Cookie cookie : existing) {
207       if (cookie.isPersistent()) {
208         store.add(cookie);
209       }
210     }
211     return store.size() < length;
212   }
213 
214   @Nullable
parseCookie(String url, String cookieHeader)215   private static Cookie parseCookie(String url, String cookieHeader) {
216     Date expiration = null;
217     boolean isSecure = false;
218 
219     String[] fields = cookieHeader.split(";", 0);
220     String cookieValue = fields[0].trim();
221 
222     for (int i = 1; i < fields.length; i++) {
223       String field = fields[i].trim();
224       if (field.startsWith(EXPIRATION_FIELD_NAME)) {
225         expiration = getExpiration(field);
226       } else if (field.equalsIgnoreCase(SECURE_ATTR_NAME)) {
227         isSecure = true;
228       }
229     }
230 
231     String hostname = getCookieHost(url);
232     if (expiration == null || expiration.compareTo(new Date()) >= 0) {
233       return new Cookie(hostname, isSecure, cookieValue, expiration);
234     }
235 
236     return null;
237   }
238 
getCookieHost(String url)239   private static String getCookieHost(String url) {
240     if (!(url.startsWith(HTTP) || url.startsWith(HTTPS))) {
241       url = HTTP + url;
242     }
243 
244     try {
245       return new URL(url).getHost();
246     } catch (MalformedURLException e) {
247       throw new IllegalArgumentException("wrong URL : " + url, e);
248     }
249   }
250 
getExpiration(String field)251   private static Date getExpiration(String field) {
252     int equalsIndex = field.indexOf("=");
253 
254     if (equalsIndex < 0) {
255       return null;
256     }
257 
258     String date = field.substring(equalsIndex + 1);
259 
260     try {
261       DateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
262       return dateFormat.parse(date);
263     } catch (ParseException e) {
264       // No-op. Try to inferFromValue additional date formats.
265     }
266 
267     try {
268       DateFormat dateFormat = new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss zzz");
269       return dateFormat.parse(date);
270     } catch (ParseException e) {
271       return null; // Was not parsed by any date formatter.
272     }
273   }
274 
275   private static class Cookie {
276     private final String mName;
277     private final String mValue;
278     private final Date mExpiration;
279     private final String mHostname;
280     private final boolean mIsSecure;
281 
Cookie(String hostname, boolean isSecure, String cookie, Date expiration)282     public Cookie(String hostname, boolean isSecure, String cookie, Date expiration) {
283       mHostname = hostname;
284       mIsSecure = isSecure;
285       mExpiration = expiration;
286 
287       int equalsIndex = cookie.indexOf("=");
288       if (equalsIndex >= 0) {
289         mName = cookie.substring(0, equalsIndex);
290         mValue = cookie.substring(equalsIndex + 1);
291       } else {
292         mName = cookie;
293         mValue = null;
294       }
295     }
296 
getName()297     public String getName() {
298       return mName;
299     }
300 
getValue()301     public String getValue() {
302       return mValue;
303     }
304 
isExpiredAt(Date date)305     public boolean isExpiredAt(Date date) {
306       return mExpiration != null && mExpiration.compareTo(date) < 0;
307     }
308 
isPersistent()309     public boolean isPersistent() {
310       return mExpiration != null;
311     }
312 
isSameHost(String host)313     public boolean isSameHost(String host) {
314       return mHostname.endsWith(host);
315     }
316 
isSecure()317     public boolean isSecure() {
318       return mIsSecure;
319     }
320   }
321 }
322