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