mirror of
				https://github.com/owncloud/android-library.git
				synced 2025-10-28 00:48:50 +00:00 
			
		
		
		
	Merge pull request #368 from owncloud/fix/cookie_handling
[Fix] Cookie handling
This commit is contained in:
		
						commit
						87a05491ab
					
				| @ -9,12 +9,13 @@ dependencies { | ||||
|     api 'com.github.AppDevNext.Logcat:LogcatCore:2.2.2' | ||||
| 
 | ||||
|     // Moshi | ||||
|     implementation ("com.squareup.moshi:moshi-kotlin:$moshiVersion") { | ||||
|     implementation("com.squareup.moshi:moshi-kotlin:$moshiVersion") { | ||||
|         exclude module: "kotlin-reflect" | ||||
|     } | ||||
|     kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion" | ||||
| 
 | ||||
|     testImplementation 'junit:junit:4.13.2' | ||||
|     testImplementation 'org.robolectric:robolectric:4.3.1' | ||||
| } | ||||
| 
 | ||||
| android { | ||||
| @ -37,4 +38,10 @@ android { | ||||
|         sourceCompatibility JavaVersion.VERSION_1_8 | ||||
|         targetCompatibility JavaVersion.VERSION_1_8 | ||||
|     } | ||||
| 
 | ||||
|     testOptions { | ||||
|         unitTests { | ||||
|             includeAndroidResources = true | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -27,6 +27,9 @@ package com.owncloud.android.lib.common; | ||||
| import android.content.Context; | ||||
| import android.net.Uri; | ||||
| 
 | ||||
| import com.owncloud.android.lib.common.http.HttpClient; | ||||
| import com.owncloud.android.lib.resources.status.GetRemoteStatusOperation; | ||||
| 
 | ||||
| public class OwnCloudClientFactory { | ||||
| 
 | ||||
|     /** | ||||
| @ -42,8 +45,14 @@ public class OwnCloudClientFactory { | ||||
| 
 | ||||
|         client.setFollowRedirects(followRedirects); | ||||
| 
 | ||||
|         client.setContext(context); | ||||
|         HttpClient.setContext(context); | ||||
|         retrieveCookiesFromMiddleware(client); | ||||
| 
 | ||||
|         return client; | ||||
|     } | ||||
| 
 | ||||
|     private static void retrieveCookiesFromMiddleware(OwnCloudClient client) { | ||||
|         final GetRemoteStatusOperation statusOperation = new GetRemoteStatusOperation(); | ||||
|         statusOperation.run(client); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -24,8 +24,6 @@ | ||||
| 
 | ||||
| package com.owncloud.android.lib.common; | ||||
| 
 | ||||
| import android.accounts.Account; | ||||
| import android.accounts.AccountManager; | ||||
| import android.accounts.AuthenticatorException; | ||||
| import android.accounts.OperationCanceledException; | ||||
| import android.content.Context; | ||||
| @ -37,7 +35,6 @@ import com.owncloud.android.lib.common.http.HttpClient; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.Iterator; | ||||
| import java.util.concurrent.ConcurrentHashMap; | ||||
| import java.util.concurrent.ConcurrentMap; | ||||
| 
 | ||||
| @ -111,6 +108,12 @@ public class SingleSessionManager { | ||||
|                     account.getBaseUri(), | ||||
|                     context.getApplicationContext(), | ||||
|                     true);    // TODO remove dependency on OwnCloudClientFactory | ||||
| 
 | ||||
|             //the next two lines are a hack because okHttpclient is used as a singleton instead of being an | ||||
|             //injected instance that can be deleted when required | ||||
|             client.clearCookies(); | ||||
|             client.clearCredentials(); | ||||
| 
 | ||||
|             client.setAccount(account); | ||||
|             HttpClient.setContext(context); | ||||
| 
 | ||||
| @ -130,7 +133,6 @@ public class SingleSessionManager { | ||||
|                 Timber.v("reusing client for session %s", sessionName); | ||||
|             } | ||||
| 
 | ||||
|             keepCookiesUpdated(context, account, client); | ||||
|             keepUriUpdated(account, client); | ||||
|         } | ||||
|         Timber.d("getClientFor finishing "); | ||||
| @ -161,32 +163,6 @@ public class SingleSessionManager { | ||||
|         Timber.d("removeClientFor finishing "); | ||||
|     } | ||||
| 
 | ||||
|     public void saveAllClients(Context context, String accountType) { | ||||
|         Timber.d("Saving sessions... "); | ||||
| 
 | ||||
|         Iterator<String> accountNames = mClientsWithKnownUsername.keySet().iterator(); | ||||
|         String accountName; | ||||
|         Account account; | ||||
|         while (accountNames.hasNext()) { | ||||
|             accountName = accountNames.next(); | ||||
|             account = new Account(accountName, accountType); | ||||
|             AccountUtils.saveClient(mClientsWithKnownUsername.get(accountName), account, context); | ||||
|         } | ||||
| 
 | ||||
|         Timber.d("All sessions saved"); | ||||
|     } | ||||
| 
 | ||||
|     private void keepCookiesUpdated(Context context, OwnCloudAccount account, OwnCloudClient reusedClient) { | ||||
|         AccountManager am = AccountManager.get(context.getApplicationContext()); | ||||
|         if (am != null && account.getSavedAccount() != null) { | ||||
|             String recentCookies = am.getUserData(account.getSavedAccount(), AccountUtils.Constants.KEY_COOKIES); | ||||
|             String previousCookies = reusedClient.getCookiesString(); | ||||
|             if (recentCookies != null && !previousCookies.equals("") && !recentCookies.equals(previousCookies)) { | ||||
|                 AccountUtils.restoreCookies(account.getSavedAccount(), reusedClient, context); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void refreshCredentialsForAccount(String accountName, OwnCloudCredentials credentials) { | ||||
|         OwnCloudClient ownCloudClient = mClientsWithKnownUsername.get(accountName); | ||||
|         if (ownCloudClient == null) { | ||||
|  | ||||
| @ -36,15 +36,10 @@ import android.net.Uri; | ||||
| import com.owncloud.android.lib.common.OwnCloudClient; | ||||
| import com.owncloud.android.lib.common.authentication.OwnCloudCredentials; | ||||
| import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory; | ||||
| import com.owncloud.android.lib.resources.files.FileUtils; | ||||
| import com.owncloud.android.lib.resources.status.OwnCloudVersion; | ||||
| import okhttp3.Cookie; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class AccountUtils { | ||||
|     /** | ||||
| @ -202,64 +197,6 @@ public class AccountUtils { | ||||
|         return username + "@" + url; | ||||
|     } | ||||
| 
 | ||||
|     public static void saveClient(OwnCloudClient client, Account savedAccount, Context context) { | ||||
|         // Account Manager | ||||
|         AccountManager ac = AccountManager.get(context.getApplicationContext()); | ||||
| 
 | ||||
|         if (client != null) { | ||||
|             String cookiesString = client.getCookiesString(); | ||||
|             if (!"".equals(cookiesString)) { | ||||
|                 ac.setUserData(savedAccount, Constants.KEY_COOKIES, cookiesString); | ||||
|                 Timber.d("Saving Cookies: %s", cookiesString); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Restore the client cookies persisted in an account stored in the system AccountManager. | ||||
|      * | ||||
|      * @param account Stored account. | ||||
|      * @param client  Client to restore cookies in. | ||||
|      * @param context Android context used to access the system AccountManager. | ||||
|      */ | ||||
|     public static void restoreCookies(Account account, OwnCloudClient client, Context context) { | ||||
|         if (account == null) { | ||||
|             Timber.d("Cannot restore cookie for null account"); | ||||
| 
 | ||||
|         } else { | ||||
|             Timber.d("Restoring cookies for %s", account.name); | ||||
| 
 | ||||
|             // Account Manager | ||||
|             AccountManager am = AccountManager.get(context.getApplicationContext()); | ||||
| 
 | ||||
|             Uri serverUri = (client.getBaseUri() != null) ? client.getBaseUri() : client.getUserFilesWebDavUri(); | ||||
| 
 | ||||
|             String cookiesString = am.getUserData(account, Constants.KEY_COOKIES); | ||||
|             if (cookiesString != null) { | ||||
|                 String[] rawCookies = cookiesString.split(";"); | ||||
|                 List<Cookie> cookieList = new ArrayList<>(rawCookies.length); | ||||
|                 for (String rawCookie : rawCookies) { | ||||
|                     rawCookie = rawCookie.replace(" ", ""); | ||||
|                     final int equalPos = rawCookie.indexOf('='); | ||||
|                     if (equalPos == -1) { | ||||
|                         continue; | ||||
|                     } | ||||
|                     cookieList.add(new Cookie.Builder() | ||||
|                             .name(rawCookie.substring(0, equalPos)) | ||||
|                             .value(rawCookie.substring(equalPos + 1)) | ||||
|                             .domain(serverUri.getHost()) | ||||
|                             .path( | ||||
|                                     serverUri.getPath().equals("") | ||||
|                                             ? File.separator | ||||
|                                             : serverUri.getPath() | ||||
|                             ) | ||||
|                             .build()); | ||||
|                 } | ||||
|                 client.setCookiesForCurrentAccount(cookieList); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static class AccountNotFoundException extends AccountsException { | ||||
| 
 | ||||
|         /** | ||||
| @ -299,11 +236,6 @@ public class AccountUtils { | ||||
| 
 | ||||
|         public static final String OAUTH_SUPPORTED_TRUE = "TRUE"; | ||||
| 
 | ||||
|         /** | ||||
|          * OC account cookies | ||||
|          */ | ||||
|         public static final String KEY_COOKIES = "oc_account_cookies"; | ||||
| 
 | ||||
|         /** | ||||
|          * OC account version | ||||
|          */ | ||||
|  | ||||
| @ -0,0 +1,63 @@ | ||||
| /* ownCloud Android Library is available under MIT license | ||||
|  *   Copyright (C) 2021 ownCloud GmbH. | ||||
|  * | ||||
|  *   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  *   of this software and associated documentation files (the "Software"), to deal | ||||
|  *   in the Software without restriction, including without limitation the rights | ||||
|  *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  *   copies of the Software, and to permit persons to whom the Software is | ||||
|  *   furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  *   The above copyright notice and this permission notice shall be included in | ||||
|  *   all copies or substantial portions of the Software. | ||||
|  * | ||||
|  *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
|  *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||||
|  *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
|  *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | ||||
|  *   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | ||||
|  *   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||||
|  *   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
|  *   THE SOFTWARE. | ||||
|  * | ||||
|  */ | ||||
| package com.owncloud.android.lib.common.http | ||||
| 
 | ||||
| import okhttp3.Cookie | ||||
| import okhttp3.CookieJar | ||||
| import okhttp3.HttpUrl | ||||
| 
 | ||||
| class CookieJarImpl( | ||||
|     private val sCookieStore: HashMap<String, List<Cookie>> | ||||
| ) : CookieJar { | ||||
| 
 | ||||
|     fun containsCookieWithName(cookies: List<Cookie>, name: String): Boolean { | ||||
|         for (cookie: Cookie in cookies) { | ||||
|             if (cookie.name == name) { | ||||
|                 return true | ||||
|             } | ||||
|         } | ||||
|         return false | ||||
|     } | ||||
| 
 | ||||
|     fun getUpdatedCookies(oldCookies: List<Cookie>, newCookies: List<Cookie>): List<Cookie> { | ||||
|         val updatedList = ArrayList<Cookie>(newCookies) | ||||
|         for (oldCookie: Cookie in oldCookies) { | ||||
|             if (!containsCookieWithName(updatedList, oldCookie.name)) { | ||||
|                 updatedList.add(oldCookie) | ||||
|             } | ||||
|         } | ||||
|         return updatedList | ||||
|     } | ||||
| 
 | ||||
|     override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) { | ||||
|         // Avoid duplicated cookies but update | ||||
|         val currentCookies: List<Cookie> = sCookieStore[url.host] ?: ArrayList() | ||||
|         val updatedCookies: List<Cookie> = getUpdatedCookies(currentCookies, cookies) | ||||
|         sCookieStore[url.host] = updatedCookies | ||||
|     } | ||||
| 
 | ||||
|     override fun loadForRequest(url: HttpUrl) = | ||||
|         sCookieStore[url.host] ?: ArrayList() | ||||
| 
 | ||||
| } | ||||
| @ -33,19 +33,18 @@ import okhttp3.CookieJar; | ||||
| import okhttp3.HttpUrl; | ||||
| import okhttp3.OkHttpClient; | ||||
| import okhttp3.Protocol; | ||||
| import okhttp3.TlsVersion; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| import javax.net.ssl.SSLContext; | ||||
| import javax.net.ssl.SSLSocketFactory; | ||||
| import javax.net.ssl.TrustManager; | ||||
| import javax.net.ssl.X509TrustManager; | ||||
| import java.security.KeyManagementException; | ||||
| import java.security.NoSuchAlgorithmException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.HashSet; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| 
 | ||||
| /** | ||||
| @ -53,6 +52,7 @@ import java.util.concurrent.TimeUnit; | ||||
|  * | ||||
|  * @author David González Verdugo | ||||
|  */ | ||||
| 
 | ||||
| public class HttpClient { | ||||
|     private static OkHttpClient sOkHttpClient; | ||||
|     private static Context sContext; | ||||
| @ -64,66 +64,13 @@ public class HttpClient { | ||||
|             try { | ||||
|                 final X509TrustManager trustManager = new AdvancedX509TrustManager( | ||||
|                         NetworkUtils.getKnownServersStore(sContext)); | ||||
| 
 | ||||
|                 SSLContext sslContext; | ||||
| 
 | ||||
|                 try { | ||||
|                     sslContext = SSLContext.getInstance("TLSv1.3"); | ||||
|                 } catch (NoSuchAlgorithmException tlsv13Exception) { | ||||
|                     try { | ||||
|                         Timber.w("TLSv1.3 is not supported in this device; falling through TLSv1.2"); | ||||
|                         sslContext = SSLContext.getInstance("TLSv1.2"); | ||||
|                     } catch (NoSuchAlgorithmException tlsv12Exception) { | ||||
|                         try { | ||||
|                             Timber.w("TLSv1.2 is not supported in this device; falling through TLSv1.1"); | ||||
|                             sslContext = SSLContext.getInstance("TLSv1.1"); | ||||
|                         } catch (NoSuchAlgorithmException tlsv11Exception) { | ||||
|                             Timber.w("TLSv1.1 is not supported in this device; falling through TLSv1.0"); | ||||
|                             sslContext = SSLContext.getInstance("TLSv1"); | ||||
|                             // should be available in any device; see reference of supported protocols in | ||||
|                             // http://developer.android.com/reference/javax/net/ssl/SSLSocket.html | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 sslContext.init(null, new TrustManager[]{trustManager}, null); | ||||
| 
 | ||||
|                 SSLSocketFactory sslSocketFactory; | ||||
| 
 | ||||
|                 sslSocketFactory = sslContext.getSocketFactory(); | ||||
| 
 | ||||
|                 final SSLSocketFactory sslSocketFactory = getNewSslSocketFactory(trustManager); | ||||
|                 // Automatic cookie handling, NOT PERSISTENT | ||||
|                 CookieJar cookieJar = new CookieJar() { | ||||
|                     @Override | ||||
|                     public void saveFromResponse(HttpUrl url, List<Cookie> cookies) { | ||||
|                         // Avoid duplicated cookies | ||||
|                         Set<Cookie> nonDuplicatedCookiesSet = new HashSet<>(cookies); | ||||
|                         List<Cookie> nonDuplicatedCookiesList = new ArrayList<>(nonDuplicatedCookiesSet); | ||||
|                 final CookieJar cookieJar = new CookieJarImpl(sCookieStore); | ||||
| 
 | ||||
|                         sCookieStore.put(url.host(), nonDuplicatedCookiesList); | ||||
|                     } | ||||
| 
 | ||||
|                     @Override | ||||
|                     public List<Cookie> loadForRequest(HttpUrl url) { | ||||
|                         List<Cookie> cookies = sCookieStore.get(url.host()); | ||||
|                         return cookies != null ? cookies : new ArrayList<>(); | ||||
|                     } | ||||
|                 }; | ||||
| 
 | ||||
|                 OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder() | ||||
|                         .addNetworkInterceptor(getLogInterceptor()) | ||||
|                         .protocols(Arrays.asList(Protocol.HTTP_1_1)) | ||||
|                         .readTimeout(HttpConstants.DEFAULT_DATA_TIMEOUT, TimeUnit.MILLISECONDS) | ||||
|                         .writeTimeout(HttpConstants.DEFAULT_DATA_TIMEOUT, TimeUnit.MILLISECONDS) | ||||
|                         .connectTimeout(HttpConstants.DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS) | ||||
|                         .followRedirects(false) | ||||
|                         .sslSocketFactory(sslSocketFactory, trustManager) | ||||
|                         .hostnameVerifier((asdf, usdf) -> true) | ||||
|                         .cookieJar(cookieJar); | ||||
|                 // TODO: Not verifying the hostname against certificate. ask owncloud security human if this is ok. | ||||
|                 //.hostnameVerifier(new BrowserCompatHostnameVerifier()); | ||||
| 
 | ||||
|                 sOkHttpClient = clientBuilder.build(); | ||||
|                 sOkHttpClient = buildNewOkHttpClient(sslSocketFactory, trustManager, cookieJar); | ||||
| 
 | ||||
|             } catch (Exception e) { | ||||
|                 Timber.e(e, "Could not setup SSL system."); | ||||
| @ -132,6 +79,60 @@ public class HttpClient { | ||||
|         return sOkHttpClient; | ||||
|     } | ||||
| 
 | ||||
|     private static SSLContext getSslContext() throws NoSuchAlgorithmException { | ||||
|         try { | ||||
|             return SSLContext.getInstance(TlsVersion.TLS_1_3.javaName()); | ||||
|         } catch (NoSuchAlgorithmException tlsv13Exception) { | ||||
|             try { | ||||
|                 Timber.w("TLSv1.3 is not supported in this device; falling through TLSv1.2"); | ||||
|                 return SSLContext.getInstance(TlsVersion.TLS_1_2.javaName()); | ||||
|             } catch (NoSuchAlgorithmException tlsv12Exception) { | ||||
|                 try { | ||||
|                     Timber.w("TLSv1.2 is not supported in this device; falling through TLSv1.1"); | ||||
|                     return SSLContext.getInstance(TlsVersion.TLS_1_1.javaName()); | ||||
|                 } catch (NoSuchAlgorithmException tlsv11Exception) { | ||||
|                     Timber.w("TLSv1.1 is not supported in this device; falling through TLSv1.0"); | ||||
|                     return SSLContext.getInstance(TlsVersion.TLS_1_0.javaName()); | ||||
|                     // should be available in any device; see reference of supported protocols in | ||||
|                     // http://developer.android.com/reference/javax/net/ssl/SSLSocket.html | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static SSLSocketFactory getNewSslSocketFactory(X509TrustManager trustManager) | ||||
|             throws NoSuchAlgorithmException, KeyManagementException { | ||||
|         final SSLContext sslContext = getSslContext(); | ||||
|         sslContext.init(null, new TrustManager[]{trustManager}, null); | ||||
|         return sslContext.getSocketFactory(); | ||||
|     } | ||||
| 
 | ||||
|     private static OkHttpClient buildNewOkHttpClient(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager, | ||||
|                                                      CookieJar cookieJar) { | ||||
|         return new OkHttpClient.Builder() | ||||
|                 .addNetworkInterceptor(getLogInterceptor()) | ||||
|                 .protocols(Collections.singletonList(Protocol.HTTP_1_1)) | ||||
|                 .readTimeout(HttpConstants.DEFAULT_DATA_TIMEOUT, TimeUnit.MILLISECONDS) | ||||
|                 .writeTimeout(HttpConstants.DEFAULT_DATA_TIMEOUT, TimeUnit.MILLISECONDS) | ||||
|                 .connectTimeout(HttpConstants.DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS) | ||||
|                 .followRedirects(false) | ||||
|                 .sslSocketFactory(sslSocketFactory, trustManager) | ||||
|                 .hostnameVerifier((asdf, usdf) -> true) | ||||
|                 .cookieJar(cookieJar) | ||||
|                 .build(); | ||||
|     } | ||||
| 
 | ||||
|     public static LogInterceptor getLogInterceptor() { | ||||
|         if (sLogInterceptor == null) { | ||||
|             sLogInterceptor = new LogInterceptor(); | ||||
|         } | ||||
|         return sLogInterceptor; | ||||
|     } | ||||
| 
 | ||||
|     public static List<Cookie> getCookiesFromUrl(HttpUrl httpUrl) { | ||||
|         return sCookieStore.get(httpUrl.host()); | ||||
|     } | ||||
| 
 | ||||
|     public Context getContext() { | ||||
|         return sContext; | ||||
|     } | ||||
| @ -140,17 +141,6 @@ public class HttpClient { | ||||
|         sContext = context; | ||||
|     } | ||||
| 
 | ||||
|     public static LogInterceptor getLogInterceptor() { | ||||
|         if (sLogInterceptor == null) { | ||||
|             sLogInterceptor = new LogInterceptor(); | ||||
|         } | ||||
|         return sLogInterceptor; | ||||
|     } | ||||
| 
 | ||||
|     public List<Cookie> getCookiesFromUrl(HttpUrl httpUrl) { | ||||
|         return sCookieStore.get(httpUrl.host()); | ||||
|     } | ||||
| 
 | ||||
|     public void clearCookies() { | ||||
|         sCookieStore.clear(); | ||||
|     } | ||||
|  | ||||
| @ -258,11 +258,6 @@ public abstract class RemoteOperation<T> implements Runnable { | ||||
| 
 | ||||
|         final RemoteOperationResult resultToSend = runOperation(); | ||||
| 
 | ||||
|         if (mAccount != null && mContext != null) { | ||||
|             // Save Client Cookies | ||||
|             AccountUtils.saveClient(mClient, mAccount, mContext); | ||||
|         } | ||||
| 
 | ||||
|         if (mListenerHandler != null && mListener != null) { | ||||
|             mListenerHandler.post(() -> | ||||
|                     mListener.onRemoteOperationFinish(RemoteOperation.this, resultToSend)); | ||||
|  | ||||
| @ -25,17 +25,14 @@ package com.owncloud.android.lib.resources.status | ||||
| 
 | ||||
| import android.net.Uri | ||||
| import com.owncloud.android.lib.common.OwnCloudClient | ||||
| import com.owncloud.android.lib.common.http.HttpConstants | ||||
| import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod | ||||
| import com.owncloud.android.lib.common.operations.RemoteOperation | ||||
| import com.owncloud.android.lib.common.operations.RemoteOperationResult | ||||
| import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode | ||||
| import com.owncloud.android.lib.resources.status.HttpScheme.HTTPS_PREFIX | ||||
| import com.owncloud.android.lib.resources.status.HttpScheme.HTTP_PREFIX | ||||
| import com.owncloud.android.lib.resources.status.HttpScheme.HTTP_SCHEME | ||||
| import org.json.JSONException | ||||
| import org.json.JSONObject | ||||
| import timber.log.Timber | ||||
| import java.net.URL | ||||
| import java.util.concurrent.TimeUnit | ||||
| import javax.net.ssl.SSLException | ||||
| 
 | ||||
| /** | ||||
|  * Checks if the server is valid | ||||
| @ -45,116 +42,44 @@ import javax.net.ssl.SSLException | ||||
|  * @author David González Verdugo | ||||
|  * @author Abel García de Prada | ||||
|  */ | ||||
| class GetRemoteStatusOperation : RemoteOperation<OwnCloudVersion>() { | ||||
|     private lateinit var latestResult: RemoteOperationResult<OwnCloudVersion> | ||||
| class GetRemoteStatusOperation : RemoteOperation<RemoteServerInfo>() { | ||||
| 
 | ||||
|     override fun run(client: OwnCloudClient): RemoteOperationResult<OwnCloudVersion> { | ||||
|     public override fun run(client: OwnCloudClient): RemoteOperationResult<RemoteServerInfo> { | ||||
|         client.baseUri = buildFullHttpsUrl(client.baseUri) | ||||
| 
 | ||||
|         val baseUriStr = client.baseUri.toString() | ||||
|         if (baseUriStr.startsWith(HTTP_PREFIX) || baseUriStr.startsWith( | ||||
|                 HTTPS_PREFIX | ||||
|             )) { | ||||
|             tryConnection(client) | ||||
|         } else { | ||||
|             client.baseUri = Uri.parse(HTTPS_PREFIX + baseUriStr) | ||||
|             val httpsSuccess = tryConnection(client) | ||||
|             if (!httpsSuccess && !latestResult.isSslRecoverableException) { | ||||
|                 Timber.d("Establishing secure connection failed, trying non secure connection") | ||||
|                 client.baseUri = Uri.parse(HTTP_PREFIX + baseUriStr) | ||||
|                 tryConnection(client) | ||||
|             } | ||||
|         var result = tryToConnect(client) | ||||
|         if (!(result.code == ResultCode.OK || result.code == ResultCode.OK_SSL) && !result.isSslRecoverableException) { | ||||
|             Timber.d("Establishing secure connection failed, trying non secure connection") | ||||
|             client.baseUri = client.baseUri.buildUpon().scheme(HTTP_SCHEME).build() | ||||
|             result = tryToConnect(client) | ||||
|         } | ||||
|         return latestResult | ||||
| 
 | ||||
|         return result | ||||
|     } | ||||
| 
 | ||||
|     private fun tryConnection(client: OwnCloudClient): Boolean { | ||||
|         var successfulConnection = false | ||||
|         val baseUrlSt = client.baseUri.toString() | ||||
|         try { | ||||
|             var getMethod = GetMethod(URL(baseUrlSt + OwnCloudClient.STATUS_PATH)).apply { | ||||
|                 setReadTimeout(TRY_CONNECTION_TIMEOUT, TimeUnit.SECONDS) | ||||
|                 setConnectionTimeout(TRY_CONNECTION_TIMEOUT, TimeUnit.SECONDS) | ||||
|             } | ||||
|             client.setFollowRedirects(false) | ||||
|             var isRedirectToNonSecureConnection = false | ||||
|             var status: Int | ||||
|             try { | ||||
|                 status = client.executeHttpMethod(getMethod) | ||||
|                 latestResult = | ||||
|                     if (isSuccess(status)) RemoteOperationResult(ResultCode.OK) | ||||
|                     else RemoteOperationResult(getMethod) | ||||
| 
 | ||||
|             } catch (sslE: SSLException) { | ||||
|                 latestResult = RemoteOperationResult(sslE) | ||||
|                 return successfulConnection | ||||
|             } | ||||
| 
 | ||||
|             var redirectedLocation = latestResult.redirectedLocation | ||||
|             while (!redirectedLocation.isNullOrEmpty() && !latestResult.isSuccess) { | ||||
|                 isRedirectToNonSecureConnection = | ||||
|                     isRedirectToNonSecureConnection || | ||||
|                             (baseUrlSt.startsWith(HTTPS_PREFIX) && redirectedLocation.startsWith( | ||||
|                                 HTTP_PREFIX | ||||
|                             )) | ||||
| 
 | ||||
|                 getMethod = GetMethod(URL(redirectedLocation)).apply { | ||||
|                     setReadTimeout(TRY_CONNECTION_TIMEOUT, TimeUnit.SECONDS) | ||||
|                     setConnectionTimeout(TRY_CONNECTION_TIMEOUT, TimeUnit.SECONDS) | ||||
|                 } | ||||
| 
 | ||||
|                 status = client.executeHttpMethod(getMethod) | ||||
|                 latestResult = RemoteOperationResult(getMethod) | ||||
|                 redirectedLocation = latestResult.redirectedLocation | ||||
|             } | ||||
| 
 | ||||
|             if (isSuccess(status)) { | ||||
|                 val respJSON = JSONObject(getMethod.getResponseBodyAsString()) | ||||
|                 if (!respJSON.getBoolean(NODE_INSTALLED)) { | ||||
|                     latestResult = RemoteOperationResult(ResultCode.INSTANCE_NOT_CONFIGURED) | ||||
|                 } else { | ||||
|                     val version = respJSON.getString(NODE_VERSION) | ||||
|                     val ocVersion = OwnCloudVersion(version) | ||||
|                     // the version object will be returned even if the version is invalid, no error code; | ||||
|                     // every app will decide how to act if (ocVersion.isVersionValid() == false) | ||||
|                     latestResult = if (isRedirectToNonSecureConnection) { | ||||
|                         RemoteOperationResult(ResultCode.OK_REDIRECT_TO_NON_SECURE_CONNECTION) | ||||
|                     } else { | ||||
|                         if (baseUrlSt.startsWith(HTTPS_PREFIX)) RemoteOperationResult(ResultCode.OK_SSL) | ||||
|                         else RemoteOperationResult(ResultCode.OK_NO_SSL) | ||||
|                     } | ||||
|                     latestResult.data = ocVersion | ||||
|                     successfulConnection = true | ||||
|                 } | ||||
|             } else { | ||||
|                 latestResult = RemoteOperationResult(getMethod) | ||||
|             } | ||||
|     private fun tryToConnect(client: OwnCloudClient): RemoteOperationResult<RemoteServerInfo> { | ||||
|         val baseUrl = client.baseUri.toString() | ||||
|         client.setFollowRedirects(false) | ||||
|         return try { | ||||
|             val requester = StatusRequester() | ||||
|             val requestResult = requester.requestAndFollowRedirects(baseUrl, client) | ||||
|             requester.handleRequestResult(requestResult, baseUrl) | ||||
|         } catch (e: JSONException) { | ||||
|             latestResult = RemoteOperationResult(ResultCode.INSTANCE_NOT_CONFIGURED) | ||||
|             RemoteOperationResult(ResultCode.INSTANCE_NOT_CONFIGURED) | ||||
|         } catch (e: Exception) { | ||||
|             latestResult = RemoteOperationResult(e) | ||||
|             RemoteOperationResult(e) | ||||
|         } | ||||
|         when { | ||||
|             latestResult.isSuccess -> Timber.i("Connection check at $baseUrlSt successful: ${latestResult.logMessage}") | ||||
| 
 | ||||
|             latestResult.isException -> | ||||
|                 Timber.e(latestResult.exception, "Connection check at $baseUrlSt: ${latestResult.logMessage}") | ||||
| 
 | ||||
|             else -> Timber.e("Connection check at $baseUrlSt failed: ${latestResult.logMessage}") | ||||
|         } | ||||
|         return successfulConnection | ||||
|     } | ||||
| 
 | ||||
|     private fun isSuccess(status: Int): Boolean = status == HttpConstants.HTTP_OK | ||||
| 
 | ||||
|     companion object { | ||||
|         /** | ||||
|          * Maximum time to wait for a response from the server when the connection is being tested, | ||||
|          * in MILLISECONDs. | ||||
|          */ | ||||
|         private const val TRY_CONNECTION_TIMEOUT: Long = 5000 | ||||
|         private const val NODE_INSTALLED = "installed" | ||||
|         private const val NODE_VERSION = "version" | ||||
|         private const val HTTPS_PREFIX = "https://" | ||||
|         private const val HTTP_PREFIX = "http://" | ||||
|         fun usesHttpOrHttps(uri: Uri) = | ||||
|             uri.toString().startsWith(HTTPS_PREFIX) || uri.toString().startsWith(HTTP_PREFIX) | ||||
| 
 | ||||
|         fun buildFullHttpsUrl(baseUri: Uri): Uri { | ||||
|             if (usesHttpOrHttps(baseUri)) { | ||||
|                 return baseUri | ||||
|             } | ||||
|             return Uri.parse("$HTTPS_PREFIX$baseUri") | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,32 @@ | ||||
| /* ownCloud Android Library is available under MIT license | ||||
| *   Copyright (C) 2021 ownCloud GmbH. | ||||
| * | ||||
| *   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| *   of this software and associated documentation files (the "Software"), to deal | ||||
| *   in the Software without restriction, including without limitation the rights | ||||
| *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| *   copies of the Software, and to permit persons to whom the Software is | ||||
| *   furnished to do so, subject to the following conditions: | ||||
| * | ||||
| *   The above copyright notice and this permission notice shall be included in | ||||
| *   all copies or substantial portions of the Software. | ||||
| * | ||||
| *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
| *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||||
| *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
| *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | ||||
| *   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | ||||
| *   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||||
| *   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| *   THE SOFTWARE. | ||||
| * | ||||
| */ | ||||
| 
 | ||||
| package com.owncloud.android.lib.resources.status | ||||
| 
 | ||||
| object HttpScheme { | ||||
|     const val HTTP_SCHEME = "http" | ||||
|     const val HTTPS_SCHEME = "https" | ||||
|     const val HTTP_PREFIX = "$HTTP_SCHEME://" | ||||
|     const val HTTPS_PREFIX = "$HTTPS_SCHEME://" | ||||
| } | ||||
| @ -0,0 +1,30 @@ | ||||
| /* ownCloud Android Library is available under MIT license | ||||
| *   Copyright (C) 2021 ownCloud GmbH. | ||||
| * | ||||
| *   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| *   of this software and associated documentation files (the "Software"), to deal | ||||
| *   in the Software without restriction, including without limitation the rights | ||||
| *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| *   copies of the Software, and to permit persons to whom the Software is | ||||
| *   furnished to do so, subject to the following conditions: | ||||
| * | ||||
| *   The above copyright notice and this permission notice shall be included in | ||||
| *   all copies or substantial portions of the Software. | ||||
| * | ||||
| *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
| *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||||
| *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
| *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | ||||
| *   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | ||||
| *   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||||
| *   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| *   THE SOFTWARE. | ||||
| * | ||||
| */ | ||||
| package com.owncloud.android.lib.resources.status | ||||
| 
 | ||||
| data class RemoteServerInfo( | ||||
|     val ownCloudVersion: OwnCloudVersion, | ||||
|     val baseUrl: String, | ||||
|     val isSecureConnection: Boolean | ||||
| ) | ||||
| @ -0,0 +1,156 @@ | ||||
| /* ownCloud Android Library is available under MIT license | ||||
| *   Copyright (C) 2021 ownCloud GmbH. | ||||
| * | ||||
| *   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| *   of this software and associated documentation files (the "Software"), to deal | ||||
| *   in the Software without restriction, including without limitation the rights | ||||
| *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| *   copies of the Software, and to permit persons to whom the Software is | ||||
| *   furnished to do so, subject to the following conditions: | ||||
| * | ||||
| *   The above copyright notice and this permission notice shall be included in | ||||
| *   all copies or substantial portions of the Software. | ||||
| * | ||||
| *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
| *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||||
| *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
| *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | ||||
| *   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | ||||
| *   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||||
| *   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| *   THE SOFTWARE. | ||||
| * | ||||
| */ | ||||
| 
 | ||||
| package com.owncloud.android.lib.resources.status | ||||
| 
 | ||||
| import com.owncloud.android.lib.common.OwnCloudClient | ||||
| import com.owncloud.android.lib.common.http.HttpConstants | ||||
| import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod | ||||
| import com.owncloud.android.lib.common.operations.RemoteOperationResult | ||||
| import com.owncloud.android.lib.resources.status.HttpScheme.HTTPS_SCHEME | ||||
| import org.json.JSONObject | ||||
| import java.net.URL | ||||
| import java.util.concurrent.TimeUnit | ||||
| 
 | ||||
| internal class StatusRequester { | ||||
| 
 | ||||
|     /** | ||||
|      * This function is ment to detect if a redirect from a secure to an unsecure connection | ||||
|      * was made. If only connections from unsecure connections to unsecure connections were made | ||||
|      * this function should not return true, because if the whole redirect chain was unsecure | ||||
|      * we assume it was a debug setup. | ||||
|      */ | ||||
|     fun isRedirectedToNonSecureConnection( | ||||
|         redirectedToNonSecureLocationBefore: Boolean, | ||||
|         baseUrl: String, | ||||
|         redirectedUrl: String | ||||
|     ) = redirectedToNonSecureLocationBefore | ||||
|             || (baseUrl.startsWith(HTTPS_SCHEME) | ||||
|             && !redirectedUrl.startsWith(HTTPS_SCHEME)) | ||||
| 
 | ||||
|     fun updateLocationWithRedirectPath(oldLocation: String, redirectedLocation: String): String { | ||||
|         /** Redirection with different endpoint. | ||||
|          * When asking for server.com/status.php and redirected to different.one/, we need to ask different.one/status.php | ||||
|          */ | ||||
|         if (redirectedLocation.endsWith('/')) { | ||||
|             return redirectedLocation.trimEnd('/') + OwnCloudClient.STATUS_PATH | ||||
|         } | ||||
| 
 | ||||
|         if (!redirectedLocation.startsWith("/")) | ||||
|             return redirectedLocation | ||||
|         val oldLocationURL = URL(oldLocation) | ||||
|         return URL(oldLocationURL.protocol, oldLocationURL.host, oldLocationURL.port, redirectedLocation).toString() | ||||
|     } | ||||
| 
 | ||||
|     private fun getGetMethod(url: String): GetMethod { | ||||
|         return GetMethod(URL(url)).apply { | ||||
|             setReadTimeout(TRY_CONNECTION_TIMEOUT, TimeUnit.SECONDS) | ||||
|             setConnectionTimeout(TRY_CONNECTION_TIMEOUT, TimeUnit.SECONDS) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     data class RequestResult( | ||||
|         val getMethod: GetMethod, | ||||
|         val status: Int, | ||||
|         val redirectedToUnsecureLocation: Boolean, | ||||
|         val lastLocation: String | ||||
|     ) | ||||
| 
 | ||||
|     fun requestAndFollowRedirects(baseLocation: String, client: OwnCloudClient): RequestResult { | ||||
|         var currentLocation = baseLocation + OwnCloudClient.STATUS_PATH | ||||
|         var redirectedToUnsecureLocation = false | ||||
|         var status: Int | ||||
| 
 | ||||
|         while (true) { | ||||
|             val getMethod = getGetMethod(currentLocation) | ||||
| 
 | ||||
|             status = client.executeHttpMethod(getMethod) | ||||
|             val result = | ||||
|                 if (status.isSuccess()) RemoteOperationResult<OwnCloudVersion>(RemoteOperationResult.ResultCode.OK) | ||||
|                 else RemoteOperationResult(getMethod) | ||||
| 
 | ||||
|             if (result.redirectedLocation.isNullOrEmpty() || result.isSuccess) { | ||||
|                 return RequestResult(getMethod, status, redirectedToUnsecureLocation, currentLocation) | ||||
|             } else { | ||||
|                 val nextLocation = updateLocationWithRedirectPath(currentLocation, result.redirectedLocation) | ||||
|                 redirectedToUnsecureLocation = | ||||
|                     isRedirectedToNonSecureConnection( | ||||
|                         redirectedToUnsecureLocation, | ||||
|                         currentLocation, | ||||
|                         nextLocation | ||||
|                     ) | ||||
|                 currentLocation = nextLocation | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun Int.isSuccess() = this == HttpConstants.HTTP_OK | ||||
| 
 | ||||
|     fun handleRequestResult( | ||||
|         requestResult: RequestResult, | ||||
|         baseUrl: String | ||||
|     ): RemoteOperationResult<RemoteServerInfo> { | ||||
|         if (!requestResult.status.isSuccess()) | ||||
|             return RemoteOperationResult(requestResult.getMethod) | ||||
| 
 | ||||
|         val respJSON = JSONObject(requestResult.getMethod.getResponseBodyAsString() ?: "") | ||||
|         if (!respJSON.getBoolean(NODE_INSTALLED)) | ||||
|             return RemoteOperationResult(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED) | ||||
| 
 | ||||
|         val ocVersion = OwnCloudVersion(respJSON.getString(NODE_VERSION)) | ||||
|         // the version object will be returned even if the version is invalid, no error code; | ||||
|         // every app will decide how to act if (ocVersion.isVersionValid() == false) | ||||
|         val result: RemoteOperationResult<RemoteServerInfo> = | ||||
|             if (requestResult.redirectedToUnsecureLocation) { | ||||
|                 RemoteOperationResult(RemoteOperationResult.ResultCode.OK_REDIRECT_TO_NON_SECURE_CONNECTION) | ||||
|             } else { | ||||
|                 if (baseUrl.startsWith(HTTPS_SCHEME)) RemoteOperationResult(RemoteOperationResult.ResultCode.OK_SSL) | ||||
|                 else RemoteOperationResult(RemoteOperationResult.ResultCode.OK_NO_SSL) | ||||
|             } | ||||
|         val finalUrl = URL(requestResult.lastLocation) | ||||
|         val finalBaseUrl = URL( | ||||
|             finalUrl.protocol, | ||||
|             finalUrl.host, | ||||
|             finalUrl.port, | ||||
|             finalUrl.file.dropLastWhile { it != '/' }.trimEnd('/') | ||||
|         ) | ||||
| 
 | ||||
|         result.data = RemoteServerInfo( | ||||
|             ownCloudVersion = ocVersion, | ||||
|             baseUrl = finalBaseUrl.toString(), | ||||
|             isSecureConnection = finalBaseUrl.protocol.startsWith(HTTPS_SCHEME) | ||||
|         ) | ||||
|         return result | ||||
|     } | ||||
| 
 | ||||
|     companion object { | ||||
|         /** | ||||
|          * Maximum time to wait for a response from the server when the connection is being tested, | ||||
|          * in milliseconds. | ||||
|          */ | ||||
|         private const val TRY_CONNECTION_TIMEOUT = 5_000L | ||||
|         private const val NODE_INSTALLED = "installed" | ||||
|         private const val NODE_VERSION = "version" | ||||
|     } | ||||
| } | ||||
| @ -25,10 +25,10 @@ package com.owncloud.android.lib.resources.status.services | ||||
| 
 | ||||
| import com.owncloud.android.lib.common.OwnCloudClient | ||||
| import com.owncloud.android.lib.common.operations.RemoteOperationResult | ||||
| import com.owncloud.android.lib.resources.status.OwnCloudVersion | ||||
| import com.owncloud.android.lib.resources.status.RemoteServerInfo | ||||
| 
 | ||||
| interface ServerInfoService { | ||||
|     fun checkPathExistence(path: String, isUserLogged: Boolean, client: OwnCloudClient): RemoteOperationResult<Boolean> | ||||
| 
 | ||||
|     fun getRemoteStatus(path: String, client: OwnCloudClient): RemoteOperationResult<OwnCloudVersion> | ||||
|     fun getRemoteStatus(path: String, client: OwnCloudClient): RemoteOperationResult<RemoteServerInfo> | ||||
| } | ||||
|  | ||||
| @ -23,7 +23,7 @@ import com.owncloud.android.lib.common.OwnCloudClient | ||||
| import com.owncloud.android.lib.common.operations.RemoteOperationResult | ||||
| import com.owncloud.android.lib.resources.files.CheckPathExistenceRemoteOperation | ||||
| import com.owncloud.android.lib.resources.status.GetRemoteStatusOperation | ||||
| import com.owncloud.android.lib.resources.status.OwnCloudVersion | ||||
| import com.owncloud.android.lib.resources.status.RemoteServerInfo | ||||
| import com.owncloud.android.lib.resources.status.services.ServerInfoService | ||||
| 
 | ||||
| class OCServerInfoService : ServerInfoService { | ||||
| @ -41,6 +41,6 @@ class OCServerInfoService : ServerInfoService { | ||||
|     override fun getRemoteStatus( | ||||
|         path: String, | ||||
|         client: OwnCloudClient | ||||
|     ): RemoteOperationResult<OwnCloudVersion> = | ||||
|     ): RemoteOperationResult<RemoteServerInfo> = | ||||
|         GetRemoteStatusOperation().execute(client) | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,84 @@ | ||||
| /* ownCloud Android Library is available under MIT license | ||||
|  *   Copyright (C) 2021 ownCloud GmbH. | ||||
|  * | ||||
|  *   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  *   of this software and associated documentation files (the "Software"), to deal | ||||
|  *   in the Software without restriction, including without limitation the rights | ||||
|  *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  *   copies of the Software, and to permit persons to whom the Software is | ||||
|  *   furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  *   The above copyright notice and this permission notice shall be included in | ||||
|  *   all copies or substantial portions of the Software. | ||||
|  * | ||||
|  *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
|  *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||||
|  *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
|  *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | ||||
|  *   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | ||||
|  *   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||||
|  *   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
|  *   THE SOFTWARE. | ||||
|  * | ||||
|  */ | ||||
| package com.owncloud.android.lib | ||||
| 
 | ||||
| import com.owncloud.android.lib.common.http.CookieJarImpl | ||||
| import okhttp3.Cookie | ||||
| import okhttp3.HttpUrl.Companion.toHttpUrl | ||||
| import org.junit.Assert.assertEquals | ||||
| import org.junit.Assert.assertFalse | ||||
| import org.junit.Assert.assertTrue | ||||
| import org.junit.Test | ||||
| 
 | ||||
| class CookieJarImplTest { | ||||
| 
 | ||||
|     private val oldCookies = listOf(COOKIE_A, COOKIE_B_OLD) | ||||
|     private val newCookies = listOf(COOKIE_B_NEW) | ||||
|     private val updatedCookies = listOf(COOKIE_A, COOKIE_B_NEW) | ||||
|     private val cookieStore = hashMapOf(SOME_HOST to oldCookies) | ||||
| 
 | ||||
|     private val cookieJarImpl = CookieJarImpl(cookieStore) | ||||
| 
 | ||||
|     @Test | ||||
|     fun `contains cookie with name - ok - true`() { | ||||
|         assertTrue(cookieJarImpl.containsCookieWithName(oldCookies, COOKIE_B_OLD.name)) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `contains cookie with name - ok - false`() { | ||||
|         assertFalse(cookieJarImpl.containsCookieWithName(newCookies, COOKIE_A.name)) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `get updated cookies - ok`() { | ||||
|         val generatedUpdatedCookies = cookieJarImpl.getUpdatedCookies(oldCookies, newCookies) | ||||
|         assertEquals(2, generatedUpdatedCookies.size) | ||||
|         assertEquals(updatedCookies[0], generatedUpdatedCookies[1]) | ||||
|         assertEquals(updatedCookies[1], generatedUpdatedCookies[0]) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `store cookie via saveFromResponse - ok`() { | ||||
|         cookieJarImpl.saveFromResponse(SOME_URL, newCookies) | ||||
|         val generatedUpdatedCookies = cookieStore[SOME_HOST] | ||||
|         assertEquals(2, generatedUpdatedCookies?.size) | ||||
|         assertEquals(updatedCookies[0], generatedUpdatedCookies?.get(1)) | ||||
|         assertEquals(updatedCookies[1], generatedUpdatedCookies?.get(0)) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `load for request - ok`() { | ||||
|         val cookies = cookieJarImpl.loadForRequest(SOME_URL) | ||||
|         assertEquals(oldCookies[0], cookies[0]) | ||||
|         assertEquals(oldCookies[1], cookies[1]) | ||||
|     } | ||||
| 
 | ||||
|     companion object { | ||||
|         const val SOME_HOST = "some.host.com" | ||||
|         val SOME_URL = "https://$SOME_HOST".toHttpUrl() | ||||
|         val COOKIE_A = Cookie.parse(SOME_URL, "CookieA=CookieValueA")!! | ||||
|         val COOKIE_B_OLD = Cookie.parse(SOME_URL, "CookieB=CookieOldValueB")!! | ||||
|         val COOKIE_B_NEW = Cookie.parse(SOME_URL, "CookieB=CookieNewValueB")!! | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,145 @@ | ||||
| /* ownCloud Android Library is available under MIT license | ||||
|  *   Copyright (C) 2021 ownCloud GmbH. | ||||
|  * | ||||
|  *   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  *   of this software and associated documentation files (the "Software"), to deal | ||||
|  *   in the Software without restriction, including without limitation the rights | ||||
|  *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  *   copies of the Software, and to permit persons to whom the Software is | ||||
|  *   furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  *   The above copyright notice and this permission notice shall be included in | ||||
|  *   all copies or substantial portions of the Software. | ||||
|  * | ||||
|  *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
|  *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||||
|  *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
|  *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | ||||
|  *   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | ||||
|  *   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||||
|  *   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
|  *   THE SOFTWARE. | ||||
|  * | ||||
|  */ | ||||
| package com.owncloud.android.lib | ||||
| 
 | ||||
| import android.net.Uri | ||||
| import android.os.Build | ||||
| import com.owncloud.android.lib.resources.status.GetRemoteStatusOperation | ||||
| import com.owncloud.android.lib.resources.status.HttpScheme.HTTPS_PREFIX | ||||
| import com.owncloud.android.lib.resources.status.HttpScheme.HTTP_PREFIX | ||||
| import org.junit.Assert.assertEquals | ||||
| import org.junit.Assert.assertFalse | ||||
| import org.junit.Assert.assertTrue | ||||
| import org.junit.Test | ||||
| import org.junit.runner.RunWith | ||||
| import org.robolectric.RobolectricTestRunner | ||||
| import org.robolectric.annotation.Config | ||||
| 
 | ||||
| @RunWith(RobolectricTestRunner::class) | ||||
| @Config(sdk = [Build.VERSION_CODES.O], manifest = Config.NONE) | ||||
| class GetRemoteStatusOperationTest { | ||||
| 
 | ||||
|     @Test | ||||
|     fun `uses http or https - ok - http`() { | ||||
|         assertTrue(GetRemoteStatusOperation.usesHttpOrHttps(Uri.parse(HTTP_SOME_OWNCLOUD))) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `uses http or https - ok - https`() { | ||||
|         assertTrue(GetRemoteStatusOperation.usesHttpOrHttps(Uri.parse(HTTPS_SOME_OWNCLOUD))) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `uses http or https - ok - no http or https`() { | ||||
|         assertFalse(GetRemoteStatusOperation.usesHttpOrHttps(Uri.parse(SOME_OWNCLOUD))) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `build full https url - ok - http`() { | ||||
|         assertEquals( | ||||
|             Uri.parse(HTTP_SOME_OWNCLOUD), | ||||
|             GetRemoteStatusOperation.buildFullHttpsUrl(Uri.parse(HTTP_SOME_OWNCLOUD)) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `build full https url - ok - https`() { | ||||
|         assertEquals( | ||||
|             Uri.parse(HTTPS_SOME_OWNCLOUD), | ||||
|             GetRemoteStatusOperation.buildFullHttpsUrl(Uri.parse(HTTPS_SOME_OWNCLOUD)) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `build full https url - ok - no prefix`() { | ||||
|         assertEquals( | ||||
|             Uri.parse(HTTPS_SOME_OWNCLOUD), | ||||
|             GetRemoteStatusOperation.buildFullHttpsUrl(Uri.parse(SOME_OWNCLOUD)) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `build full https url - ok - no https with subdir`() { | ||||
|         assertEquals( | ||||
|             Uri.parse(HTTPS_SOME_OWNCLOUD_WITH_SUBDIR), | ||||
|             GetRemoteStatusOperation.buildFullHttpsUrl( | ||||
|                 Uri.parse(HTTPS_SOME_OWNCLOUD_WITH_SUBDIR) | ||||
|             ) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `build full https url - ok - no prefix with subdir`() { | ||||
|         assertEquals( | ||||
|             Uri.parse(HTTPS_SOME_OWNCLOUD_WITH_SUBDIR), | ||||
|             GetRemoteStatusOperation.buildFullHttpsUrl( | ||||
|                 Uri.parse(SOME_OWNCLOUD_WITH_SUBDIR) | ||||
|             ) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `build full https url - ok - ip`() { | ||||
|         assertEquals(Uri.parse(HTTPS_SOME_IP), GetRemoteStatusOperation.buildFullHttpsUrl(Uri.parse(SOME_IP))) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `build full https url - ok - http ip`() { | ||||
|         assertEquals(Uri.parse(HTTP_SOME_IP), GetRemoteStatusOperation.buildFullHttpsUrl(Uri.parse(HTTP_SOME_IP))) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `build full https url - ok - ip with port`() { | ||||
|         assertEquals( | ||||
|             Uri.parse(HTTPS_SOME_IP_WITH_PORT), | ||||
|             GetRemoteStatusOperation.buildFullHttpsUrl(Uri.parse(SOME_IP_WITH_PORT)) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `build full https url - ok - ip with http and port`() { | ||||
|         assertEquals( | ||||
|             Uri.parse(HTTP_SOME_IP_WITH_PORT), | ||||
|             GetRemoteStatusOperation.buildFullHttpsUrl(Uri.parse(HTTP_SOME_IP_WITH_PORT)) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     companion object { | ||||
|         const val SOME_OWNCLOUD = "some_owncloud.com" | ||||
|         const val HTTP_SOME_OWNCLOUD = "$HTTP_PREFIX$SOME_OWNCLOUD" | ||||
|         const val HTTPS_SOME_OWNCLOUD = "$HTTPS_PREFIX$SOME_OWNCLOUD" | ||||
| 
 | ||||
|         const val SOME_OWNCLOUD_WITH_SUBDIR = "some_owncloud.com/subdir" | ||||
|         const val HTTP_SOME_OWNCLOUD_WITH_SUBDIR = "$HTTP_PREFIX$SOME_OWNCLOUD_WITH_SUBDIR" | ||||
|         const val HTTPS_SOME_OWNCLOUD_WITH_SUBDIR = "$HTTPS_PREFIX$SOME_OWNCLOUD_WITH_SUBDIR" | ||||
| 
 | ||||
|         const val SOME_IP = "184.123.185.12" | ||||
|         const val HTTP_SOME_IP = "$HTTP_PREFIX$SOME_IP" | ||||
|         const val HTTPS_SOME_IP = "$HTTPS_PREFIX$SOME_IP" | ||||
| 
 | ||||
|         const val SOME_IP_WITH_PORT = "184.123.185.12:5678" | ||||
|         const val HTTP_SOME_IP_WITH_PORT = "$HTTP_PREFIX$SOME_IP_WITH_PORT" | ||||
|         const val HTTPS_SOME_IP_WITH_PORT = "$HTTPS_PREFIX$SOME_IP_WITH_PORT" | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,104 @@ | ||||
| /* ownCloud Android Library is available under MIT license | ||||
| *   Copyright (C) 2021 ownCloud GmbH. | ||||
| * | ||||
| *   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| *   of this software and associated documentation files (the "Software"), to deal | ||||
| *   in the Software without restriction, including without limitation the rights | ||||
| *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| *   copies of the Software, and to permit persons to whom the Software is | ||||
| *   furnished to do so, subject to the following conditions: | ||||
| * | ||||
| *   The above copyright notice and this permission notice shall be included in | ||||
| *   all copies or substantial portions of the Software. | ||||
| * | ||||
| *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
| *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||||
| *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
| *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | ||||
| *   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | ||||
| *   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||||
| *   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| *   THE SOFTWARE. | ||||
| * | ||||
| */ | ||||
| 
 | ||||
| package com.owncloud.android.lib | ||||
| 
 | ||||
| import com.owncloud.android.lib.resources.status.StatusRequester | ||||
| import org.junit.Assert.assertEquals | ||||
| import org.junit.Assert.assertFalse | ||||
| import org.junit.Assert.assertTrue | ||||
| import org.junit.Test | ||||
| 
 | ||||
| class StatusRequesterTest { | ||||
|     private val requester = StatusRequester() | ||||
| 
 | ||||
|     @Test | ||||
|     fun `update location - ok - absolute path`() { | ||||
|         val newLocation = requester.updateLocationWithRedirectPath(TEST_DOMAIN, "$TEST_DOMAIN$SUB_PATH") | ||||
|         assertEquals("$TEST_DOMAIN$SUB_PATH", newLocation) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `update location - ok - smaller absolute path`() { | ||||
|         val newLocation = requester.updateLocationWithRedirectPath("$TEST_DOMAIN$SUB_PATH", TEST_DOMAIN) | ||||
|         assertEquals(TEST_DOMAIN, newLocation) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `update location - ok - relative path`() { | ||||
|         val newLocation = requester.updateLocationWithRedirectPath(TEST_DOMAIN, SUB_PATH) | ||||
|         assertEquals("$TEST_DOMAIN$SUB_PATH", newLocation) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `update location - ok - replace relative path`() { | ||||
|         val newLocation = requester.updateLocationWithRedirectPath("$TEST_DOMAIN/some/other/subdir", SUB_PATH) | ||||
|         assertEquals("$TEST_DOMAIN$SUB_PATH", newLocation) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `check redirect to unsecure connection - ok - redirect to http`() { | ||||
|         assertTrue( | ||||
|             requester.isRedirectedToNonSecureConnection(false, SECURE_DOMAIN, UNSECURE_DOMAIN | ||||
|             ) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `check redirect to unsecure connection - ko - redirect to https from http`() { | ||||
|         assertFalse( | ||||
|             requester.isRedirectedToNonSecureConnection(false, UNSECURE_DOMAIN, SECURE_DOMAIN | ||||
|             ) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `check redirect to unsecure connection - ko - from https to https`() { | ||||
|         assertFalse( | ||||
|             requester.isRedirectedToNonSecureConnection(false, SECURE_DOMAIN, SECURE_DOMAIN) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `check redirect to unsecure connection - ok - from https to https with previous http`() { | ||||
|         assertTrue( | ||||
|             requester.isRedirectedToNonSecureConnection(true, SECURE_DOMAIN, SECURE_DOMAIN) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     fun `check redirect to unsecure connection - ok - from http to http`() { | ||||
|         assertFalse( | ||||
|             requester.isRedirectedToNonSecureConnection(false, UNSECURE_DOMAIN, UNSECURE_DOMAIN) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     companion object { | ||||
|         const val TEST_DOMAIN = "https://cloud.somewhere.com" | ||||
|         const val SUB_PATH = "/subdir" | ||||
| 
 | ||||
|         const val SECURE_DOMAIN = "https://cloud.somewhere.com" | ||||
|         const val UNSECURE_DOMAIN = "http://somewhereelse.org" | ||||
|     } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user