mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 12:53:55 +01:00
Remove the data-client app adapter implementation (#5499)
* logErrorsInsteadOfCrashing only ever returned false, so remove it and simplify logger * Removed unused getMediaWikiBaseUrl() * Removed getDesiredLeadImageDp() from commons app adapter, since it's unused * Inlined the isLoggedIn() method of the app adapter * Inline the app adapter username/password * Removed the unused getRestbaseUriFormat() from the commons app adapter * Remove references to the data-client SharedPreferenceCookieManager * Manage our own OkHttpClient and remove the AppAdapter implementation
This commit is contained in:
parent
ab9e57f5be
commit
8db0b54929
57 changed files with 422 additions and 518 deletions
|
|
@ -1,77 +0,0 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import okhttp3.OkHttpClient;
|
||||
import org.wikipedia.AppAdapter;
|
||||
import org.wikipedia.dataclient.SharedPreferenceCookieManager;
|
||||
import org.wikipedia.json.GsonMarshaller;
|
||||
import org.wikipedia.json.GsonUnmarshaller;
|
||||
|
||||
public class CommonsAppAdapter extends AppAdapter {
|
||||
private final int DEFAULT_THUMB_SIZE = 640;
|
||||
private final String COOKIE_STORE_NAME = "cookie_store";
|
||||
|
||||
private final SessionManager sessionManager;
|
||||
private final JsonKvStore preferences;
|
||||
|
||||
CommonsAppAdapter(@NonNull SessionManager sessionManager, @NonNull JsonKvStore preferences) {
|
||||
this.sessionManager = sessionManager;
|
||||
this.preferences = preferences;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMediaWikiBaseUrl() {
|
||||
return BuildConfig.COMMONS_URL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRestbaseUriFormat() {
|
||||
return BuildConfig.COMMONS_URL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OkHttpClient getOkHttpClient() {
|
||||
return OkHttpConnectionFactory.getClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDesiredLeadImageDp() {
|
||||
return DEFAULT_THUMB_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoggedIn() {
|
||||
return sessionManager.isUserLoggedIn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserName() {
|
||||
return sessionManager.getUserName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return sessionManager.getPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SharedPreferenceCookieManager getCookies() {
|
||||
if (!preferences.contains(COOKIE_STORE_NAME)) {
|
||||
return null;
|
||||
}
|
||||
return GsonUnmarshaller.unmarshal(SharedPreferenceCookieManager.class,
|
||||
preferences.getString(COOKIE_STORE_NAME, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCookies(@NonNull SharedPreferenceCookieManager cookies) {
|
||||
preferences.putString(COOKIE_STORE_NAME, GsonMarshaller.marshal(cookies));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean logErrorsInsteadOfCrashing() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -41,6 +41,7 @@ import fr.free.nrw.commons.media.CustomOkHttpNetworkFetcher;
|
|||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import fr.free.nrw.commons.upload.FileUtils;
|
||||
import fr.free.nrw.commons.utils.ConfigUtils;
|
||||
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.internal.functions.Functions;
|
||||
|
|
@ -58,7 +59,6 @@ import org.acra.annotation.AcraCore;
|
|||
import org.acra.annotation.AcraDialog;
|
||||
import org.acra.annotation.AcraMailSender;
|
||||
import org.acra.data.StringFormat;
|
||||
import org.wikipedia.AppAdapter;
|
||||
import org.wikipedia.language.AppLanguageLookUpTable;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
|
@ -94,6 +94,9 @@ public class CommonsApplication extends MultiDexApplication {
|
|||
@Named("default_preferences")
|
||||
JsonKvStore defaultPrefs;
|
||||
|
||||
@Inject
|
||||
CommonsCookieJar cookieJar;
|
||||
|
||||
@Inject
|
||||
CustomOkHttpNetworkFetcher customOkHttpNetworkFetcher;
|
||||
|
||||
|
|
@ -161,8 +164,6 @@ public class CommonsApplication extends MultiDexApplication {
|
|||
.getCommonsApplicationComponent()
|
||||
.inject(this);
|
||||
|
||||
AppAdapter.set(new CommonsAppAdapter(sessionManager, defaultPrefs));
|
||||
|
||||
initTimber();
|
||||
|
||||
if (!defaultPrefs.getBoolean("has_user_manually_removed_location")) {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
|
@ -15,28 +15,27 @@ import okhttp3.Response;
|
|||
import okhttp3.ResponseBody;
|
||||
import okhttp3.logging.HttpLoggingInterceptor;
|
||||
import okhttp3.logging.HttpLoggingInterceptor.Level;
|
||||
import org.wikipedia.dataclient.SharedPreferenceCookieManager;
|
||||
import org.wikipedia.dataclient.okhttp.HttpStatusException;
|
||||
import timber.log.Timber;
|
||||
|
||||
public final class OkHttpConnectionFactory {
|
||||
private static final String CACHE_DIR_NAME = "okhttp-cache";
|
||||
private static final long NET_CACHE_SIZE = 64 * 1024 * 1024;
|
||||
@NonNull private static final Cache NET_CACHE = new Cache(new File(CommonsApplication.getInstance().getCacheDir(),
|
||||
CACHE_DIR_NAME), NET_CACHE_SIZE);
|
||||
|
||||
@NonNull
|
||||
private static final OkHttpClient CLIENT = createClient();
|
||||
public static OkHttpClient CLIENT;
|
||||
|
||||
@NonNull public static OkHttpClient getClient() {
|
||||
@NonNull public static OkHttpClient getClient(final CommonsCookieJar cookieJar) {
|
||||
if (CLIENT == null) {
|
||||
CLIENT = createClient(cookieJar);
|
||||
}
|
||||
return CLIENT;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static OkHttpClient createClient() {
|
||||
private static OkHttpClient createClient(final CommonsCookieJar cookieJar) {
|
||||
return new OkHttpClient.Builder()
|
||||
.cookieJar(SharedPreferenceCookieManager.getInstance())
|
||||
.cache(NET_CACHE)
|
||||
.cookieJar(cookieJar)
|
||||
.cache(new Cache(new File(CommonsApplication.getInstance().getCacheDir(), CACHE_DIR_NAME), NET_CACHE_SIZE))
|
||||
.connectTimeout(120, TimeUnit.SECONDS)
|
||||
.writeTimeout(120, TimeUnit.SECONDS)
|
||||
.readTimeout(120, TimeUnit.SECONDS)
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ package fr.free.nrw.commons.auth.csrf
|
|||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import org.wikipedia.AppAdapter
|
||||
import org.wikipedia.dataclient.SharedPreferenceCookieManager
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
||||
import fr.free.nrw.commons.auth.login.LoginClient
|
||||
import fr.free.nrw.commons.auth.login.LoginCallback
|
||||
|
|
@ -19,7 +17,8 @@ import java.util.concurrent.Executors.newSingleThreadExecutor
|
|||
class CsrfTokenClient(
|
||||
private val sessionManager: SessionManager,
|
||||
private val csrfTokenInterface: CsrfTokenInterface,
|
||||
private val loginClient: LoginClient
|
||||
private val loginClient: LoginClient,
|
||||
private val logoutClient: LogoutClient
|
||||
) {
|
||||
private var retries = 0
|
||||
private var csrfTokenCall: Call<MwQueryResponse?>? = null
|
||||
|
|
@ -27,8 +26,8 @@ class CsrfTokenClient(
|
|||
@Throws(Throwable::class)
|
||||
fun getTokenBlocking(): String {
|
||||
var token = ""
|
||||
val userName = AppAdapter.get().getUserName()
|
||||
val password = AppAdapter.get().getPassword()
|
||||
val userName = sessionManager.userName ?: ""
|
||||
val password = sessionManager.password ?: ""
|
||||
|
||||
for (retry in 0 until MAX_RETRIES_OF_LOGIN_BLOCKING) {
|
||||
try {
|
||||
|
|
@ -47,7 +46,7 @@ class CsrfTokenClient(
|
|||
}
|
||||
|
||||
token = response.body()!!.query()!!.csrfToken()!!
|
||||
if (AppAdapter.get().isLoggedIn() && token == ANON_TOKEN) {
|
||||
if (sessionManager.isUserLoggedIn && token == ANON_TOKEN) {
|
||||
throw RuntimeException("App believes we're logged in, but got anonymous token.")
|
||||
}
|
||||
break
|
||||
|
|
@ -66,7 +65,7 @@ class CsrfTokenClient(
|
|||
fun request(service: CsrfTokenInterface, cb: Callback): Call<MwQueryResponse?> =
|
||||
requestToken(service, object : Callback {
|
||||
override fun success(token: String?) {
|
||||
if (AppAdapter.get().isLoggedIn() && token == ANON_TOKEN) {
|
||||
if (sessionManager.isUserLoggedIn && token == ANON_TOKEN) {
|
||||
retryWithLogin(cb) {
|
||||
RuntimeException("App believes we're logged in, but got anonymous token.")
|
||||
}
|
||||
|
|
@ -102,11 +101,11 @@ class CsrfTokenClient(
|
|||
}
|
||||
|
||||
private fun retryWithLogin(callback: Callback, caught: () -> Throwable?) {
|
||||
val userName = AppAdapter.get().getUserName()
|
||||
val password = AppAdapter.get().getPassword()
|
||||
val userName = sessionManager.userName
|
||||
val password = sessionManager.password
|
||||
if (retries < MAX_RETRIES && !userName.isNullOrEmpty() && !password.isNullOrEmpty()) {
|
||||
retries++
|
||||
SharedPreferenceCookieManager.getInstance().clearAllCookies()
|
||||
logoutClient.logout()
|
||||
login(userName, password, callback) {
|
||||
Timber.i("retrying...")
|
||||
cancel()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package fr.free.nrw.commons.auth.csrf
|
||||
|
||||
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieStorage
|
||||
import javax.inject.Inject
|
||||
|
||||
class LogoutClient @Inject constructor(private val store: CommonsCookieStorage) {
|
||||
fun logout() = store.clear()
|
||||
}
|
||||
|
|
@ -257,8 +257,8 @@ public class CommonsApplicationModule {
|
|||
|
||||
@Named("username")
|
||||
@Provides
|
||||
public String provideLoggedInUsername() {
|
||||
return Objects.toString(AppAdapter.get().getUserName(), "");
|
||||
public String provideLoggedInUsername(SessionManager sessionManager) {
|
||||
return Objects.toString(sessionManager.getUserName(), "");
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ import fr.free.nrw.commons.actions.PageEditClient;
|
|||
import fr.free.nrw.commons.actions.PageEditInterface;
|
||||
import fr.free.nrw.commons.actions.ThanksInterface;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient;
|
||||
import fr.free.nrw.commons.auth.csrf.CsrfTokenInterface;
|
||||
import fr.free.nrw.commons.auth.csrf.LogoutClient;
|
||||
import fr.free.nrw.commons.auth.login.LoginClient;
|
||||
import fr.free.nrw.commons.auth.login.LoginInterface;
|
||||
import fr.free.nrw.commons.category.CategoryInterface;
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient;
|
||||
|
|
@ -30,6 +33,8 @@ import fr.free.nrw.commons.upload.WikiBaseInterface;
|
|||
import fr.free.nrw.commons.upload.depicts.DepictsInterface;
|
||||
import fr.free.nrw.commons.wikidata.CommonsServiceFactory;
|
||||
import fr.free.nrw.commons.wikidata.WikidataInterface;
|
||||
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar;
|
||||
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieStorage;
|
||||
import java.io.File;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
|
@ -40,11 +45,8 @@ import okhttp3.HttpUrl;
|
|||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.logging.HttpLoggingInterceptor;
|
||||
import okhttp3.logging.HttpLoggingInterceptor.Level;
|
||||
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient;
|
||||
import org.wikipedia.AppAdapter;
|
||||
import org.wikipedia.dataclient.WikiSite;
|
||||
import org.wikipedia.json.GsonUtil;
|
||||
import fr.free.nrw.commons.auth.login.LoginClient;
|
||||
import timber.log.Timber;
|
||||
|
||||
@Module
|
||||
|
|
@ -78,8 +80,8 @@ public class NetworkingModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
public CommonsServiceFactory serviceFactory() {
|
||||
return new CommonsServiceFactory(AppAdapter.get().getOkHttpClient());
|
||||
public CommonsServiceFactory serviceFactory(CommonsCookieJar cookieJar) {
|
||||
return new CommonsServiceFactory(OkHttpConnectionFactory.getClient(cookieJar));
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -107,12 +109,27 @@ public class NetworkingModule {
|
|||
gson);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public CommonsCookieStorage provideCookieStorage(
|
||||
@Named("default_preferences") JsonKvStore preferences) {
|
||||
CommonsCookieStorage cookieStorage = new CommonsCookieStorage(preferences);
|
||||
cookieStorage.load();
|
||||
return cookieStorage;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public CommonsCookieJar provideCookieJar(CommonsCookieStorage storage) {
|
||||
return new CommonsCookieJar(storage);
|
||||
}
|
||||
|
||||
@Named(NAMED_COMMONS_CSRF)
|
||||
@Provides
|
||||
@Singleton
|
||||
public CsrfTokenClient provideCommonsCsrfTokenClient(SessionManager sessionManager,
|
||||
CsrfTokenInterface tokenInterface, LoginClient loginClient) {
|
||||
return new CsrfTokenClient(sessionManager, tokenInterface, loginClient);
|
||||
CsrfTokenInterface tokenInterface, LoginClient loginClient, LogoutClient logoutClient) {
|
||||
return new CsrfTokenClient(sessionManager, tokenInterface, loginClient, logoutClient);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
package fr.free.nrw.commons.wikidata.cookies
|
||||
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.CookieJar
|
||||
import okhttp3.HttpUrl
|
||||
|
||||
class CommonsCookieJar(private val cookieStorage: CommonsCookieStorage) : CookieJar {
|
||||
override fun loadForRequest(url: HttpUrl): List<Cookie> {
|
||||
val cookieList = mutableListOf<Cookie>()
|
||||
val domain: String = url.toUri().getAuthority()
|
||||
|
||||
cookieStorage.domains.forEach { domainSpec ->
|
||||
if (domain.endsWith(domainSpec, true)) {
|
||||
buildCookieList(cookieList, cookieStorage[domainSpec], null)
|
||||
} else if (domainSpec.endsWith("commons.wikimedia.org")) {
|
||||
// For sites outside the wikipedia.org domain, transfer the centralauth cookies
|
||||
// from commons.wikimedia.org unconditionally.
|
||||
buildCookieList(cookieList, cookieStorage[domainSpec], "centralauth_")
|
||||
}
|
||||
}
|
||||
return cookieList
|
||||
}
|
||||
|
||||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
||||
if (cookies.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
var cookieJarModified = false
|
||||
cookies.forEach { cookie ->
|
||||
// Default to the URI's domain if cookie's domain is not explicitly set
|
||||
val domainSpec = cookie.domainSpec(url)
|
||||
if (!cookieStorage.contains(domainSpec)) {
|
||||
cookieStorage[domainSpec] = mutableListOf()
|
||||
}
|
||||
|
||||
val cookieList = cookieStorage[domainSpec]
|
||||
if (cookie.expiredOrDeleted()) {
|
||||
cookieJarModified = cookieList.removeAll { it.name == cookie.name }
|
||||
} else {
|
||||
val i = cookieList.iterator()
|
||||
var exists = false
|
||||
while (i.hasNext()) {
|
||||
val c = i.next()
|
||||
if (c == cookie) {
|
||||
// an identical cookie already exists, so we don't need to update it.
|
||||
exists = true
|
||||
break
|
||||
} else if (c.name == cookie.name) {
|
||||
// it's a cookie with the same name, but different contents, so remove the
|
||||
// current cookie, so that the new one will be added.
|
||||
i.remove()
|
||||
}
|
||||
}
|
||||
if (!exists) {
|
||||
cookieList.add(cookie)
|
||||
cookieJarModified = true
|
||||
}
|
||||
}
|
||||
cookieStorage[domainSpec] = cookieList
|
||||
}
|
||||
|
||||
if (cookieJarModified) {
|
||||
cookieStorage.save()
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildCookieList(
|
||||
outList: MutableList<Cookie>, inList: MutableList<Cookie>, prefix: String?
|
||||
) {
|
||||
var cookieJarModified = false
|
||||
|
||||
val i = inList.iterator()
|
||||
while (i.hasNext()) {
|
||||
val cookie = i.next()
|
||||
if (prefix != null && !cookie.name.startsWith(prefix)) {
|
||||
continue
|
||||
}
|
||||
// But wait, is the cookie expired?
|
||||
if (cookie.expiresAt < System.currentTimeMillis()) {
|
||||
i.remove()
|
||||
cookieJarModified = true
|
||||
} else {
|
||||
outList.add(cookie)
|
||||
}
|
||||
}
|
||||
|
||||
if (cookieJarModified) {
|
||||
cookieStorage.save()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Cookie.expiredOrDeleted(): Boolean =
|
||||
expiresAt < System.currentTimeMillis() || "deleted" == value
|
||||
|
||||
private fun Cookie.domainSpec(url: HttpUrl): String =
|
||||
domain.ifEmpty { url.toUri().getAuthority() }
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package fr.free.nrw.commons.wikidata.cookies
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import org.wikipedia.dataclient.WikiSite
|
||||
|
||||
private const val COOKIE_STORE = "cookie_store"
|
||||
|
||||
class CommonsCookieStorage(private val preferences: JsonKvStore? = null) {
|
||||
private val gson = GsonBuilder().registerTypeAdapter(
|
||||
CommonsCookieStorage::class.java,
|
||||
CookieStorageTypeAdapter()
|
||||
).create()
|
||||
private val cookieMap: MutableMap<String, List<Cookie>> = mutableMapOf()
|
||||
|
||||
val domains : Set<String> get() = cookieMap.keys.toSet()
|
||||
|
||||
operator fun set(domainSpec: String, cookies: MutableList<Cookie>) =
|
||||
cookieMap.put(domainSpec, cookies.toList())
|
||||
|
||||
operator fun get(domainSpec: String): MutableList<Cookie> =
|
||||
cookieMap[domainSpec]?.toMutableList() ?: mutableListOf()
|
||||
|
||||
fun clear() {
|
||||
cookieMap.clear()
|
||||
save()
|
||||
}
|
||||
|
||||
fun load() {
|
||||
cookieMap.clear()
|
||||
val json = preferences!!.getString(COOKIE_STORE, null)
|
||||
if (!json.isNullOrEmpty()) {
|
||||
val serializedData = gson.fromJson(json, CommonsCookieStorage::class.java)
|
||||
cookieMap.putAll(serializedData.cookieMap)
|
||||
}
|
||||
}
|
||||
|
||||
fun save() =
|
||||
preferences!!.putString(COOKIE_STORE, gson.toJson(this))
|
||||
|
||||
fun contains(domainSpec: String): Boolean =
|
||||
cookieMap.containsKey(domainSpec)
|
||||
|
||||
companion object {
|
||||
fun from(map: Map<String, List<Cookie>>) = CommonsCookieStorage().apply {
|
||||
cookieMap.clear()
|
||||
cookieMap.putAll(map)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CookieStorageTypeAdapter : TypeAdapter<CommonsCookieStorage>() {
|
||||
override fun write(out: JsonWriter, value: CommonsCookieStorage) {
|
||||
out.beginObject()
|
||||
value.domains.forEach { domain ->
|
||||
out.name(domain).beginArray()
|
||||
value[domain].forEach { out.value(it.toString()) }
|
||||
out.endArray()
|
||||
}
|
||||
out.endObject()
|
||||
}
|
||||
|
||||
override fun read(input: JsonReader): CommonsCookieStorage {
|
||||
val map = mutableMapOf<String, List<Cookie>>()
|
||||
input.beginObject()
|
||||
while (input.hasNext()) {
|
||||
val key = input.nextName()
|
||||
map[key] = input.readCookies((WikiSite.DEFAULT_SCHEME + "://" + key).toHttpUrlOrNull())
|
||||
}
|
||||
input.endObject()
|
||||
return CommonsCookieStorage.from(map)
|
||||
}
|
||||
|
||||
private fun JsonReader.readCookies(url: HttpUrl?): MutableList<Cookie> {
|
||||
val list = mutableListOf<Cookie>()
|
||||
beginArray()
|
||||
while (hasNext()) {
|
||||
val str = nextString()
|
||||
url?.let {
|
||||
val element: Cookie? = Cookie.parse(url, str)
|
||||
element?.let { list += element }
|
||||
}
|
||||
}
|
||||
endArray()
|
||||
return list
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue