diff --git a/owncloudComLibrary/build.gradle b/owncloudComLibrary/build.gradle index 7053882e..5c6bc1a7 100644 --- a/owncloudComLibrary/build.gradle +++ b/owncloudComLibrary/build.gradle @@ -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 + } + } } diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/OwnCloudClientFactory.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/OwnCloudClientFactory.java index d78e4d6a..948e8a07 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/OwnCloudClientFactory.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/OwnCloudClientFactory.java @@ -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); + } } diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/SingleSessionManager.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/SingleSessionManager.java index e011a7fa..c410cc90 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/SingleSessionManager.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/SingleSessionManager.java @@ -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 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) { diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/accounts/AccountUtils.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/accounts/AccountUtils.java index d8983ed9..32e062ae 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/accounts/AccountUtils.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/accounts/AccountUtils.java @@ -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 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 */ diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/CookieJarImpl.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/CookieJarImpl.kt new file mode 100644 index 00000000..ae76424e --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/CookieJarImpl.kt @@ -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> +) : CookieJar { + + fun containsCookieWithName(cookies: List, name: String): Boolean { + for (cookie: Cookie in cookies) { + if (cookie.name == name) { + return true + } + } + return false + } + + fun getUpdatedCookies(oldCookies: List, newCookies: List): List { + val updatedList = ArrayList(newCookies) + for (oldCookie: Cookie in oldCookies) { + if (!containsCookieWithName(updatedList, oldCookie.name)) { + updatedList.add(oldCookie) + } + } + return updatedList + } + + override fun saveFromResponse(url: HttpUrl, cookies: List) { + // Avoid duplicated cookies but update + val currentCookies: List = sCookieStore[url.host] ?: ArrayList() + val updatedCookies: List = getUpdatedCookies(currentCookies, cookies) + sCookieStore[url.host] = updatedCookies + } + + override fun loadForRequest(url: HttpUrl) = + sCookieStore[url.host] ?: ArrayList() + +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpClient.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpClient.java index 06e8056d..046dc711 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpClient.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpClient.java @@ -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 cookies) { - // Avoid duplicated cookies - Set nonDuplicatedCookiesSet = new HashSet<>(cookies); - List nonDuplicatedCookiesList = new ArrayList<>(nonDuplicatedCookiesSet); + final CookieJar cookieJar = new CookieJarImpl(sCookieStore); - sCookieStore.put(url.host(), nonDuplicatedCookiesList); - } - - @Override - public List loadForRequest(HttpUrl url) { - List 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 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 getCookiesFromUrl(HttpUrl httpUrl) { - return sCookieStore.get(httpUrl.host()); - } - public void clearCookies() { sCookieStore.clear(); } diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperation.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperation.java index 321907b6..ef2a8321 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperation.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperation.java @@ -258,11 +258,6 @@ public abstract class RemoteOperation 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)); diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/GetRemoteStatusOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/GetRemoteStatusOperation.kt index f47544b1..eeb2aadf 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/GetRemoteStatusOperation.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/GetRemoteStatusOperation.kt @@ -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() { - private lateinit var latestResult: RemoteOperationResult +class GetRemoteStatusOperation : RemoteOperation() { - override fun run(client: OwnCloudClient): RemoteOperationResult { + public override fun run(client: OwnCloudClient): RemoteOperationResult { + 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 { + 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") + } } } diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/HttpScheme.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/HttpScheme.kt new file mode 100644 index 00000000..ff3e3662 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/HttpScheme.kt @@ -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://" +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/RemoteServerInfo.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/RemoteServerInfo.kt new file mode 100644 index 00000000..7163fd4a --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/RemoteServerInfo.kt @@ -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 +) diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/StatusRequester.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/StatusRequester.kt new file mode 100644 index 00000000..8706a185 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/StatusRequester.kt @@ -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(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 { + 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 = + 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" + } +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/services/ServerInfoService.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/services/ServerInfoService.kt index 121f617f..911b2b80 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/services/ServerInfoService.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/services/ServerInfoService.kt @@ -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 - fun getRemoteStatus(path: String, client: OwnCloudClient): RemoteOperationResult + fun getRemoteStatus(path: String, client: OwnCloudClient): RemoteOperationResult } diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/services/implementation/OCServerInfoService.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/services/implementation/OCServerInfoService.kt index 65b85479..0dbae093 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/services/implementation/OCServerInfoService.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/status/services/implementation/OCServerInfoService.kt @@ -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 = + ): RemoteOperationResult = GetRemoteStatusOperation().execute(client) } diff --git a/owncloudComLibrary/src/test/java/com/owncloud/android/lib/CookieJarImplTest.kt b/owncloudComLibrary/src/test/java/com/owncloud/android/lib/CookieJarImplTest.kt new file mode 100644 index 00000000..7e1d7691 --- /dev/null +++ b/owncloudComLibrary/src/test/java/com/owncloud/android/lib/CookieJarImplTest.kt @@ -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")!! + } +} diff --git a/owncloudComLibrary/src/test/java/com/owncloud/android/lib/GetRemoteStatusOperationTest.kt b/owncloudComLibrary/src/test/java/com/owncloud/android/lib/GetRemoteStatusOperationTest.kt new file mode 100644 index 00000000..375e9aa5 --- /dev/null +++ b/owncloudComLibrary/src/test/java/com/owncloud/android/lib/GetRemoteStatusOperationTest.kt @@ -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" + } +} diff --git a/owncloudComLibrary/src/test/java/com/owncloud/android/lib/StatusRequesterTest.kt b/owncloudComLibrary/src/test/java/com/owncloud/android/lib/StatusRequesterTest.kt new file mode 100644 index 00000000..20b1c668 --- /dev/null +++ b/owncloudComLibrary/src/test/java/com/owncloud/android/lib/StatusRequesterTest.kt @@ -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" + } +}