content: /*
      Android Asynchronous Http Client
      Copyright (c) 2011 James Smith <james@loopj.com>
      https://github.com/android-async-http/android-async-http
  
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
      You may obtain a copy of the License at
  
          https://www.apache.org/licenses/LICENSE-2.0
  
      Unless required by applicable law or agreed to in writing, software
      distributed under the License is distributed on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      See the License for the specific language governing permissions and
      limitations under the License.
  */
  
  package com.loopj.android.http;
  
  import android.content.Context;
  import android.content.SharedPreferences;
  import android.text.TextUtils;
  
  import java.io.ByteArrayInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.io.ObjectInputStream;
  import java.io.ObjectOutputStream;
  import java.util.ArrayList;
  import java.util.Date;
  import java.util.List;
  import java.util.Locale;
  import java.util.concurrent.ConcurrentHashMap;
  
  import cz.msebera.android.httpclient.client.CookieStore;
  import cz.msebera.android.httpclient.cookie.Cookie;
  
  /**
   * A persistent cookie store which implements the Apache HttpClient {@link CookieStore} interface.
   * Cookies are stored and will persist on the user's device between application sessions since they
   * are serialized and stored in {@link SharedPreferences}. <p>&nbsp;</p> Instances of this class are
   * designed to be used with {@link AsyncHttpClient#setCookieStore}, but can also be used with a
   * regular old apache HttpClient/HttpContext if you prefer.
   */
  public class PersistentCookieStore implements CookieStore {
      private static final String LOG_TAG = "PersistentCookieStore";
      private static final String COOKIE_PREFS = "CookiePrefsFile";
      private static final String COOKIE_NAME_STORE = "names";
      private static final String COOKIE_NAME_PREFIX = "cookie_";
      private final ConcurrentHashMap<String, Cookie> cookies;
      private final SharedPreferences cookiePrefs;
      private boolean omitNonPersistentCookies = false;
  
      /**
       * Construct a persistent cookie store.
       *
       * @param context Context to attach cookie store to
       */
      public PersistentCookieStore(Context context) {
          cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
          cookies = new ConcurrentHashMap<String, Cookie>();
  
          // Load any previously stored cookies into the store
          String storedCookieNames = cookiePrefs.getString(COOKIE_NAME_STORE, null);
          if (storedCookieNames != null) {
              String[] cookieNames = TextUtils.split(storedCookieNames, ",");
              for (String name : cookieNames) {
                  String encodedCookie = cookiePrefs.getString(COOKIE_NAME_PREFIX + name, null);
                  if (encodedCookie != null) {
                      Cookie decodedCookie = decodeCookie(encodedCookie);
                      if (decodedCookie != null) {
                          cookies.put(name, decodedCookie);
                      }
                  }
              }
  
              // Clear out expired cookies
              clearExpired(new Date());
          }
      }
  
      @Override
      public void addCookie(Cookie cookie) {
          if (omitNonPersistentCookies && !cookie.isPersistent())
              return;
          String name = cookie.getName() + cookie.getDomain();
  
          // Save cookie into local store, or remove if expired
          if (!cookie.isExpired(new Date())) {
              cookies.put(name, cookie);
          } else {
              cookies.remove(name);
          }
  
          // Save cookie into persistent store
          SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
          prefsWriter.putString(COOKIE_NAME_STORE, TextUtils.join(",", cookies.keySet()));
          prefsWriter.putString(COOKIE_NAME_PREFIX + name, encodeCookie(new SerializableCookie(cookie)));
          prefsWriter.apply();
      }
  
      @Override
      public void clear() {
          // Clear cookies from persistent store
          SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
          for (String name : cookies.keySet()) {
              prefsWriter.remove(COOKIE_NAME_PREFIX + name);
          }
          prefsWriter.remove(COOKIE_NAME_STORE);
          prefsWriter.apply();
  
          // Clear cookies from local store
          cookies.clear();
      }
  
      @Override
      public boolean clearExpired(Date date) {
          boolean clearedAny = false;
          SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
  
          for (ConcurrentHashMap.Entry<String, Cookie> entry : cookies.entrySet()) {
              String name = entry.getKey();
              Cookie cookie = entry.getValue();
              if (cookie.isExpired(date)) {
                  // Clear cookies from local store
                  cookies.remove(name);
  
                  // Clear cookies from persistent store
                  prefsWriter.remove(COOKIE_NAME_PREFIX + name);
  
                  // We've cleared at least one
                  clearedAny = true;
              }
          }
  
          // Update names in persistent store
          if (clearedAny) {
              prefsWriter.putString(COOKIE_NAME_STORE, TextUtils.join(",", cookies.keySet()));
          }
          prefsWriter.apply();
  
          return clearedAny;
      }
  
      @Override
      public List<Cookie> getCookies() {
          return new ArrayList<Cookie>(cookies.values());
      }
  
      /**
       * Will make PersistentCookieStore instance ignore Cookies, which are non-persistent by
       * signature (`Cookie.isPersistent`)
       *
       * @param omitNonPersistentCookies true if non-persistent cookies should be omited
       */
      public void setOmitNonPersistentCookies(boolean omitNonPersistentCookies) {
          this.omitNonPersistentCookies = omitNonPersistentCookies;
      }
  
      /**
       * Non-standard helper method, to delete cookie
       *
       * @param cookie cookie to be removed
       */
      public void deleteCookie(Cookie cookie) {
          String name = cookie.getName() + cookie.getDomain();
          cookies.remove(name);
          SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
          prefsWriter.remove(COOKIE_NAME_PREFIX + name);
          prefsWriter.apply();
      }
  
      /**
       * Serializes Cookie object into String
       *
       * @param cookie cookie to be encoded, can be null
       * @return cookie encoded as String
       */
      protected String encodeCookie(SerializableCookie cookie) {
          if (cookie == null)
              return null;
          ByteArrayOutputStream os = new ByteArrayOutputStream();
          try {
              ObjectOutputStream outputStream = new ObjectOutputStream(os);
              outputStream.writeObject(cookie);
          } catch (IOException e) {
              AsyncHttpClient.log.d(LOG_TAG, "IOException in encodeCookie", e);
              return null;
          }
  
          return byteArrayToHexString(os.toByteArray());
      }
  
      /**
       * Returns cookie decoded from cookie string
       *
       * @param cookieString string of cookie as returned from http request
       * @return decoded cookie or null if exception occured
       */
      protected Cookie decodeCookie(String cookieString) {
          byte[] bytes = hexStringToByteArray(cookieString);
          ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
          Cookie cookie = null;
          try {
              ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
              cookie = ((SerializableCookie) objectInputStream.readObject()).getCookie();
          } catch (IOException e) {
              AsyncHttpClient.log.d(LOG_TAG, "IOException in decodeCookie", e);
          } catch (ClassNotFoundException e) {
              AsyncHttpClient.log.d(LOG_TAG, "ClassNotFoundException in decodeCookie", e);
          }
  
          return cookie;
      }
  
      /**
       * Using some super basic byte array &lt;-&gt; hex conversions so we don't have to rely on any
       * large Base64 libraries. Can be overridden if you like!
       *
       * @param bytes byte array to be converted
       * @return string containing hex values
       */
      protected String byteArrayToHexString(byte[] bytes) {
          StringBuilder sb = new StringBuilder(bytes.length * 2);
          for (byte element : bytes) {
              int v = element & 0xff;
              if (v < 16) {
                  sb.append('0');
              }
              sb.append(Integer.toHexString(v));
          }
          return sb.toString().toUpperCase(Locale.US);
      }
  
      /**
       * Converts hex values from strings to byte arra
       *
       * @param hexString string of hex-encoded values
       * @return decoded byte array
       */
      protected byte[] hexStringToByteArray(String hexString) {
          int len = hexString.length();
          byte[] data = new byte[len / 2];
          for (int i = 0; i < len; i += 2) {
              data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16));
          }
          return data;
      }
  }