/* * Copyright � 2016 TIBCO Software,Inc.All rights reserved. * http://community.jaspersoft.com/project/jaspermobile-android * * Unless you have purchased a commercial license agreement from TIBCO Jaspersoft, * the following license terms apply: * * This program is part of TIBCO Jaspersoft Mobile for Android. * * TIBCO Jaspersoft Mobile is free software:you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation,either version 3of the License,or * (at your option)any later version. * * TIBCO Jaspersoft Mobile is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY;without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with TIBCO Jaspersoft Mobile for Android.If not,see * <http://www.gnu.org/licenses/lgpl>. */ package com.jaspersoft.android.jaspermobile.network.cookie; import android.content.Context; import android.content.SharedPreferences; import java.net.CookieStore; import java.net.HttpCookie; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import timber.log.Timber; /** * Source <a href="https://gist.github.com/franmontiel/ed12a2295566b7076161">https://gist.github.com/franmontiel/ed12a2295566b7076161</a> * @author Fran Montiel */ final class PersistentCookieStore implements CookieStore { private static final String TAG = PersistentCookieStore.class .getSimpleName(); // Persistence private static final String SP_COOKIE_STORE = "cookieStore"; private static final String SP_KEY_DELIMITER = "|"; // Unusual char in URL private static final String SP_KEY_DELIMITER_REGEX = "\\" + SP_KEY_DELIMITER; private SharedPreferences sharedPreferences; // In memory private Map<URI, Set<HttpCookie>> allCookies; public PersistentCookieStore(Context context) { sharedPreferences = context.getSharedPreferences(SP_COOKIE_STORE, Context.MODE_PRIVATE); loadAllFromPersistence(); } private void loadAllFromPersistence() { allCookies = new HashMap<URI, Set<HttpCookie>>(); Map<String, ?> allPairs = sharedPreferences.getAll(); for (Map.Entry<String, ?> entry : allPairs.entrySet()) { String[] uriAndName = entry.getKey().split(SP_KEY_DELIMITER_REGEX, 2); try { URI uri = new URI(uriAndName[0]); String encodedCookie = (String) entry.getValue(); HttpCookie cookie = new SerializableHttpCookie() .decode(encodedCookie); Set<HttpCookie> targetCookies = allCookies.get(uri); if (targetCookies == null) { targetCookies = new HashSet<HttpCookie>(); allCookies.put(uri, targetCookies); } // Repeated cookies cannot exist in persistence // targetCookies.remove(cookie) targetCookies.add(cookie); } catch (URISyntaxException e) { Timber.w(TAG, e); } } } @Override public synchronized void add(URI uri, HttpCookie cookie) { uri = cookieUri(uri, cookie); Set<HttpCookie> targetCookies = allCookies.get(uri); if (targetCookies == null) { targetCookies = new HashSet<HttpCookie>(); allCookies.put(uri, targetCookies); } targetCookies.remove(cookie); targetCookies.add(cookie); saveToPersistence(uri, cookie); } /** * Get the real URI from the cookie "domain" and "path" attributes, if they * are not set then uses the URI provided (coming from the response) * * @param uri * @param cookie * @return */ private static URI cookieUri(URI uri, HttpCookie cookie) { URI cookieUri = uri; if (cookie.getDomain() != null) { // Remove the starting dot character of the domain, if exists (e.g: .domain.com -> domain.com) String domain = cookie.getDomain(); if (domain.charAt(0) == '.') { domain = domain.substring(1); } try { cookieUri = new URI(uri.getScheme() == null ? "http" : uri.getScheme(), domain, cookie.getPath() == null ? "/" : cookie.getPath(), null); } catch (URISyntaxException e) { Timber.w(TAG, e); } } return cookieUri; } private void saveToPersistence(URI uri, HttpCookie cookie) { SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putString(uri.toString() + SP_KEY_DELIMITER + cookie.getName(), new SerializableHttpCookie().encode(cookie)); editor.apply(); } @Override public synchronized List<HttpCookie> get(URI uri) { return getValidCookies(uri); } @Override public synchronized List<HttpCookie> getCookies() { List<HttpCookie> allValidCookies = new ArrayList<HttpCookie>(); for (URI storedUri : allCookies.keySet()) { allValidCookies.addAll(getValidCookies(storedUri)); } return allValidCookies; } private List<HttpCookie> getValidCookies(URI uri) { List<HttpCookie> targetCookies = new ArrayList<HttpCookie>(); // If the stored URI does not have a path then it must match any URI in // the same domain for (URI storedUri : allCookies.keySet()) { // Check ith the domains match according to RFC 6265 if (checkDomainsMatch(storedUri.getHost(), uri.getHost())) { // Check if the paths match according to RFC 6265 if (checkPathsMatch(storedUri.getPath(), uri.getPath())) { targetCookies.addAll(allCookies.get(storedUri)); } } } // Check it there are expired cookies and remove them if (!targetCookies.isEmpty()) { List<HttpCookie> cookiesToRemoveFromPersistence = new ArrayList<HttpCookie>(); for (Iterator<HttpCookie> it = targetCookies.iterator(); it .hasNext(); ) { HttpCookie currentCookie = it.next(); if (currentCookie != null && currentCookie.hasExpired()) { cookiesToRemoveFromPersistence.add(currentCookie); it.remove(); } } if (!cookiesToRemoveFromPersistence.isEmpty()) { removeFromPersistence(uri, cookiesToRemoveFromPersistence); } } return targetCookies; } /* http://tools.ietf.org/html/rfc6265#section-5.1.3 A string domain-matches a given domain string if at least one of the following conditions hold: o The domain string and the string are identical. (Note that both the domain string and the string will have been canonicalized to lower case at this point.) o All of the following conditions hold: * The domain string is a suffix of the string. * The last character of the string that is not included in the domain string is a %x2E (".") character. * The string is a host name (i.e., not an IP address). */ private boolean checkDomainsMatch(String cookieHost, String requestHost) { return requestHost.equals(cookieHost) || requestHost.endsWith("." + cookieHost); } /* http://tools.ietf.org/html/rfc6265#section-5.1.4 A request-path path-matches a given cookie-path if at least one of the following conditions holds: o The cookie-path and the request-path are identical. o The cookie-path is a prefix of the request-path, and the last character of the cookie-path is %x2F ("/"). o The cookie-path is a prefix of the request-path, and the first character of the request-path that is not included in the cookie- path is a %x2F ("/") character. */ private boolean checkPathsMatch(String cookiePath, String requestPath) { return requestPath.equals(cookiePath) || (requestPath.startsWith(cookiePath) && cookiePath.charAt(cookiePath.length() - 1) == '/') || (requestPath.startsWith(cookiePath) && requestPath.substring(cookiePath.length()).charAt(0) == '/'); } private void removeFromPersistence(URI uri, List<HttpCookie> cookiesToRemove) { SharedPreferences.Editor editor = sharedPreferences.edit(); for (HttpCookie cookieToRemove : cookiesToRemove) { editor.remove(uri.toString() + SP_KEY_DELIMITER + cookieToRemove.getName()); } editor.apply(); } @Override public synchronized List<URI> getURIs() { return new ArrayList<URI>(allCookies.keySet()); } @Override public synchronized boolean remove(URI uri, HttpCookie cookie) { Set<HttpCookie> targetCookies = allCookies.get(uri); boolean cookieRemoved = targetCookies != null && targetCookies .remove(cookie); if (cookieRemoved) { removeFromPersistence(uri, cookie); } return cookieRemoved; } private void removeFromPersistence(URI uri, HttpCookie cookieToRemove) { SharedPreferences.Editor editor = sharedPreferences.edit(); editor.remove(uri.toString() + SP_KEY_DELIMITER + cookieToRemove.getName()); editor.apply(); } @Override public synchronized boolean removeAll() { allCookies.clear(); removeAllFromPersistence(); return true; } private void removeAllFromPersistence() { sharedPreferences.edit().clear().apply(); } }