1
0
mirror of https://github.com/owncloud/android-library.git synced 2026-08-20 04:43:01 +00:00

Reordered packages

This commit is contained in:
David A. Velasco
2014-02-14 12:04:41 +01:00
parent 815fbba486
commit 6c4d342610
54 changed files with 208 additions and 303 deletions
@@ -0,0 +1,267 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
* Copyright (C) 2012 Bartek Przybylski
*
* 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;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpConnectionManager;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.HttpVersion;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthPolicy;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.http.HttpStatus;
import org.apache.http.params.CoreProtocolPNames;
import com.owncloud.android.lib.common.network.BearerAuthScheme;
import com.owncloud.android.lib.common.network.BearerCredentials;
import com.owncloud.android.lib.common.network.WebdavUtils;
import android.net.Uri;
import android.util.Log;
public class OwnCloudClient extends HttpClient {
private static final int MAX_REDIRECTIONS_COUNT = 3;
private Uri mUri;
private Uri mWebdavUri;
private Credentials mCredentials;
private boolean mFollowRedirects;
private String mSsoSessionCookie;
final private static String TAG = OwnCloudClient.class.getSimpleName();
public static final String USER_AGENT = "Android-ownCloud";
static private byte[] sExhaustBuffer = new byte[1024];
/**
* Constructor
*/
public OwnCloudClient(HttpConnectionManager connectionMgr) {
super(connectionMgr);
Log.d(TAG, "Creating OwnCloudClient");
getParams().setParameter(HttpMethodParams.USER_AGENT, USER_AGENT);
getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);
mFollowRedirects = true;
mSsoSessionCookie = null;
}
public void setBearerCredentials(String accessToken) {
AuthPolicy.registerAuthScheme(BearerAuthScheme.AUTH_POLICY, BearerAuthScheme.class);
List<String> authPrefs = new ArrayList<String>(1);
authPrefs.add(BearerAuthScheme.AUTH_POLICY);
getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
mCredentials = new BearerCredentials(accessToken);
getState().setCredentials(AuthScope.ANY, mCredentials);
mSsoSessionCookie = null;
}
public void setBasicCredentials(String username, String password) {
List<String> authPrefs = new ArrayList<String>(1);
authPrefs.add(AuthPolicy.BASIC);
getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
getParams().setAuthenticationPreemptive(true);
mCredentials = new UsernamePasswordCredentials(username, password);
getState().setCredentials(AuthScope.ANY, mCredentials);
mSsoSessionCookie = null;
}
public void setSsoSessionCookie(String accessToken) {
getParams().setAuthenticationPreemptive(false);
getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES);
mSsoSessionCookie = accessToken;
mCredentials = null;
}
/**
* Check if a file exists in the OC server
*
* TODO replace with ExistenceOperation
*
* @return 'true' if the file exists; 'false' it doesn't exist
* @throws Exception When the existence could not be determined
*/
public boolean existsFile(String path) throws IOException, HttpException {
HeadMethod head = new HeadMethod(mWebdavUri.toString() + WebdavUtils.encodePath(path));
try {
int status = executeMethod(head);
Log.d(TAG, "HEAD to " + path + " finished with HTTP status " + status + ((status != HttpStatus.SC_OK)?"(FAIL)":""));
exhaustResponse(head.getResponseBodyAsStream());
return (status == HttpStatus.SC_OK);
} finally {
head.releaseConnection(); // let the connection available for other methods
}
}
/**
* Requests the received method with the received timeout (milliseconds).
*
* Executes the method through the inherited HttpClient.executedMethod(method).
*
* Sets the socket and connection timeouts only for the method received.
*
* The timeouts are both in milliseconds; 0 means 'infinite'; < 0 means 'do not change the default'
*
* @param method HTTP method request.
* @param readTimeout Timeout to set for data reception
* @param conntionTimout Timeout to set for connection establishment
*/
public int executeMethod(HttpMethodBase method, int readTimeout, int connectionTimeout) throws HttpException, IOException {
int oldSoTimeout = getParams().getSoTimeout();
int oldConnectionTimeout = getHttpConnectionManager().getParams().getConnectionTimeout();
try {
if (readTimeout >= 0) {
method.getParams().setSoTimeout(readTimeout); // this should be enough...
getParams().setSoTimeout(readTimeout); // ... but this looks like necessary for HTTPS
}
if (connectionTimeout >= 0) {
getHttpConnectionManager().getParams().setConnectionTimeout(connectionTimeout);
}
return executeMethod(method);
} finally {
getParams().setSoTimeout(oldSoTimeout);
getHttpConnectionManager().getParams().setConnectionTimeout(oldConnectionTimeout);
}
}
@Override
public int executeMethod(HttpMethod method) throws IOException, HttpException {
boolean customRedirectionNeeded = false;
try {
method.setFollowRedirects(mFollowRedirects);
} catch (Exception e) {
//if (mFollowRedirects) Log_OC.d(TAG, "setFollowRedirects failed for " + method.getName() + " method, custom redirection will be used if needed");
customRedirectionNeeded = mFollowRedirects;
}
if (mSsoSessionCookie != null && mSsoSessionCookie.length() > 0) {
method.addRequestHeader("Cookie", mSsoSessionCookie);
}
int status = super.executeMethod(method);
int redirectionsCount = 0;
while (customRedirectionNeeded &&
redirectionsCount < MAX_REDIRECTIONS_COUNT &&
( status == HttpStatus.SC_MOVED_PERMANENTLY ||
status == HttpStatus.SC_MOVED_TEMPORARILY ||
status == HttpStatus.SC_TEMPORARY_REDIRECT)
) {
Header location = method.getResponseHeader("Location");
if (location != null) {
Log.d(TAG, "Location to redirect: " + location.getValue());
method.setURI(new URI(location.getValue(), true));
status = super.executeMethod(method);
redirectionsCount++;
} else {
Log.d(TAG, "No location to redirect!");
status = HttpStatus.SC_NOT_FOUND;
}
}
return status;
}
/**
* Exhausts a not interesting HTTP response. Encouraged by HttpClient documentation.
*
* @param responseBodyAsStream InputStream with the HTTP response to exhaust.
*/
public void exhaustResponse(InputStream responseBodyAsStream) {
if (responseBodyAsStream != null) {
try {
while (responseBodyAsStream.read(sExhaustBuffer) >= 0);
responseBodyAsStream.close();
} catch (IOException io) {
Log.e(TAG, "Unexpected exception while exhausting not interesting HTTP response; will be IGNORED", io);
}
}
}
/**
* Sets the connection and wait-for-data timeouts to be applied by default to the methods performed by this client.
*/
public void setDefaultTimeouts(int defaultDataTimeout, int defaultConnectionTimeout) {
getParams().setSoTimeout(defaultDataTimeout);
getHttpConnectionManager().getParams().setConnectionTimeout(defaultConnectionTimeout);
}
/**
* Sets the Webdav URI for the helper methods that receive paths as parameters, instead of full URLs
* @param uri
*/
public void setWebdavUri(Uri uri) {
mWebdavUri = uri;
}
public Uri getWebdavUri() {
return mWebdavUri;
}
/**
* Sets the base URI for the helper methods that receive paths as parameters, instead of full URLs
* @param uri
*/
public void setBaseUri(Uri uri) {
mUri = uri;
}
public Uri getBaseUri() {
return mUri;
}
public final Credentials getCredentials() {
return mCredentials;
}
public final String getSsoSessionCookie() {
return mSsoSessionCookie;
}
public void setFollowRedirects(boolean followRedirects) {
mFollowRedirects = followRedirects;
}
}
@@ -0,0 +1,163 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
*
* 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;
import java.io.IOException;
import java.security.GeneralSecurityException;
import com.owncloud.android.lib.common.accounts.AccountTypeUtils;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
import com.owncloud.android.lib.common.network.NetworkUtils;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
public class OwnCloudClientFactory {
final private static String TAG = OwnCloudClientFactory.class.getSimpleName();
/** Default timeout for waiting data from the server */
public static final int DEFAULT_DATA_TIMEOUT = 60000;
/** Default timeout for establishing a connection */
public static final int DEFAULT_CONNECTION_TIMEOUT = 60000;
/**
* Creates a OwnCloudClient setup for an ownCloud account
*
* Do not call this method from the main thread.
*
* @param account The ownCloud account
* @param appContext Android application context
* @return A OwnCloudClient object ready to be used
* @throws AuthenticatorException If the authenticator failed to get the authorization token for the account.
* @throws OperationCanceledException If the authenticator operation was cancelled while getting the authorization token for the account.
* @throws IOException If there was some I/O error while getting the authorization token for the account.
* @throws AccountNotFoundException If 'account' is unknown for the AccountManager
*/
public static OwnCloudClient createOwnCloudClient (Account account, Context appContext) throws OperationCanceledException, AuthenticatorException, IOException, AccountNotFoundException {
//Log_OC.d(TAG, "Creating OwnCloudClient associated to " + account.name);
Uri webdavUri = Uri.parse(AccountUtils.constructFullURLForAccount(appContext, account));
Uri uri = Uri.parse(AccountUtils.constructBasicURLForAccount(appContext, account));
AccountManager am = AccountManager.get(appContext);
boolean isOauth2 = am.getUserData(account, AccountUtils.Constants.KEY_SUPPORTS_OAUTH2) != null; // TODO avoid calling to getUserData here
boolean isSamlSso = am.getUserData(account, AccountUtils.Constants.KEY_SUPPORTS_SAML_WEB_SSO) != null;
OwnCloudClient client = createOwnCloudClient(webdavUri, appContext, !isSamlSso);
client.setBaseUri(uri);
if (isOauth2) {
String accessToken = am.blockingGetAuthToken(account, AccountTypeUtils.getAuthTokenTypeAccessToken(account.type), false);
client.setBearerCredentials(accessToken); // TODO not assume that the access token is a bearer token
} else if (isSamlSso) { // TODO avoid a call to getUserData here
String accessToken = am.blockingGetAuthToken(account, AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(account.type), false);
client.setSsoSessionCookie(accessToken);
} else {
String username = account.name.substring(0, account.name.lastIndexOf('@'));
//String password = am.getPassword(account);
String password = am.blockingGetAuthToken(account, AccountTypeUtils.getAuthTokenTypePass(account.type), false);
client.setBasicCredentials(username, password);
}
return client;
}
public static OwnCloudClient createOwnCloudClient (Account account, Context appContext, Activity currentActivity) throws OperationCanceledException, AuthenticatorException, IOException, AccountNotFoundException {
Uri webdavUri = Uri.parse(AccountUtils.constructFullURLForAccount(appContext, account));
Uri uri = Uri.parse(AccountUtils.constructBasicURLForAccount(appContext, account));
AccountManager am = AccountManager.get(appContext);
boolean isOauth2 = am.getUserData(account, AccountUtils.Constants.KEY_SUPPORTS_OAUTH2) != null; // TODO avoid calling to getUserData here
boolean isSamlSso = am.getUserData(account, AccountUtils.Constants.KEY_SUPPORTS_SAML_WEB_SSO) != null;
OwnCloudClient client = createOwnCloudClient(webdavUri, appContext, !isSamlSso);
client.setBaseUri(uri);
if (isOauth2) { // TODO avoid a call to getUserData here
AccountManagerFuture<Bundle> future = am.getAuthToken(account, AccountTypeUtils.getAuthTokenTypeAccessToken(account.type), null, currentActivity, null, null);
Bundle result = future.getResult();
String accessToken = result.getString(AccountManager.KEY_AUTHTOKEN);
if (accessToken == null) throw new AuthenticatorException("WTF!");
client.setBearerCredentials(accessToken); // TODO not assume that the access token is a bearer token
} else if (isSamlSso) { // TODO avoid a call to getUserData here
AccountManagerFuture<Bundle> future = am.getAuthToken(account, AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(account.type), null, currentActivity, null, null);
Bundle result = future.getResult();
String accessToken = result.getString(AccountManager.KEY_AUTHTOKEN);
if (accessToken == null) throw new AuthenticatorException("WTF!");
client.setSsoSessionCookie(accessToken);
} else {
String username = account.name.substring(0, account.name.lastIndexOf('@'));
//String password = am.getPassword(account);
//String password = am.blockingGetAuthToken(account, MainApp.getAuthTokenTypePass(), false);
AccountManagerFuture<Bundle> future = am.getAuthToken(account, AccountTypeUtils.getAuthTokenTypePass(account.type), null, currentActivity, null, null);
Bundle result = future.getResult();
String password = result.getString(AccountManager.KEY_AUTHTOKEN);
client.setBasicCredentials(username, password);
}
return client;
}
/**
* Creates a OwnCloudClient to access a URL and sets the desired parameters for ownCloud client connections.
*
* @param uri URL to the ownCloud server
* @param context Android context where the OwnCloudClient is being created.
* @return A OwnCloudClient object ready to be used
*/
public static OwnCloudClient createOwnCloudClient(Uri uri, Context context, boolean followRedirects) {
try {
NetworkUtils.registerAdvancedSslContext(true, context);
} catch (GeneralSecurityException e) {
Log.e(TAG, "Advanced SSL Context could not be loaded. Default SSL management in the system will be used for HTTPS connections", e);
} catch (IOException e) {
Log.e(TAG, "The local server truststore could not be read. Default SSL management in the system will be used for HTTPS connections", e);
}
OwnCloudClient client = new OwnCloudClient(NetworkUtils.getMultiThreadedConnManager());
client.setDefaultTimeouts(DEFAULT_DATA_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT);
client.setWebdavUri(uri);
client.setFollowRedirects(followRedirects);
return client;
}
}
@@ -0,0 +1,50 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
* Copyright (C) 2012 Bartek Przybylski
*
* 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.accounts;
/**
* @author masensio
* @author David A. Velasco
*/
public class AccountTypeUtils {
public static String getAuthTokenTypePass(String accountType) {
return accountType + ".password";
}
public static String getAuthTokenTypeAccessToken(String accountType) {
return accountType + ".oauth2.access_token";
}
public static String getAuthTokenTypeRefreshToken(String accountType) {
return accountType + ".oauth2.refresh_token";
}
public static String getAuthTokenTypeSamlSessionCookie(String accountType) {
return accountType + ".saml.web_sso.session_cookie";
}
}
@@ -0,0 +1,164 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
* Copyright (C) 2012 Bartek Przybylski
*
* 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.accounts;
import com.owncloud.android.lib.resources.status.OwnCloudVersion;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountsException;
import android.content.Context;
public class AccountUtils {
public static final String WEBDAV_PATH_1_2 = "/webdav/owncloud.php";
public static final String WEBDAV_PATH_2_0 = "/files/webdav.php";
public static final String WEBDAV_PATH_4_0 = "/remote.php/webdav";
private static final String ODAV_PATH = "/remote.php/odav";
private static final String SAML_SSO_PATH = "/remote.php/webdav";
public static final String CARDDAV_PATH_2_0 = "/apps/contacts/carddav.php";
public static final String CARDDAV_PATH_4_0 = "/remote/carddav.php";
public static final String STATUS_PATH = "/status.php";
/**
* Returns the proper URL path to access the WebDAV interface of an ownCloud server,
* according to its version and the authorization method used.
*
* @param version Version of ownCloud server.
* @param supportsOAuth If true, access with OAuth 2 authorization is considered.
* @param supportsSamlSso If true, and supportsOAuth is false, access with SAML-based single-sign-on is considered.
* @return WebDAV path for given OC version, null if OC version unknown
*/
public static String getWebdavPath(OwnCloudVersion version, boolean supportsOAuth, boolean supportsSamlSso) {
if (version != null) {
if (supportsOAuth) {
return ODAV_PATH;
}
if (supportsSamlSso) {
return SAML_SSO_PATH;
}
if (version.compareTo(OwnCloudVersion.owncloud_v4) >= 0)
return WEBDAV_PATH_4_0;
if (version.compareTo(OwnCloudVersion.owncloud_v3) >= 0
|| version.compareTo(OwnCloudVersion.owncloud_v2) >= 0)
return WEBDAV_PATH_2_0;
if (version.compareTo(OwnCloudVersion.owncloud_v1) >= 0)
return WEBDAV_PATH_1_2;
}
return null;
}
/**
* Constructs full url to host and webdav resource basing on host version
* @param context
* @param account
* @return url or null on failure
* @throws AccountNotFoundException When 'account' is unknown for the AccountManager
*/
public static String constructFullURLForAccount(Context context, Account account) throws AccountNotFoundException {
AccountManager ama = AccountManager.get(context);
String baseurl = ama.getUserData(account, Constants.KEY_OC_BASE_URL);
String strver = ama.getUserData(account, Constants.KEY_OC_VERSION);
boolean supportsOAuth = (ama.getUserData(account, Constants.KEY_SUPPORTS_OAUTH2) != null);
boolean supportsSamlSso = (ama.getUserData(account, Constants.KEY_SUPPORTS_SAML_WEB_SSO) != null);
OwnCloudVersion ver = new OwnCloudVersion(strver);
String webdavpath = getWebdavPath(ver, supportsOAuth, supportsSamlSso);
if (baseurl == null || webdavpath == null)
throw new AccountNotFoundException(account, "Account not found", null);
return baseurl + webdavpath;
}
/**
* Extracts url server from the account
* @param context
* @param account
* @return url server or null on failure
* @throws AccountNotFoundException When 'account' is unknown for the AccountManager
*/
public static String constructBasicURLForAccount(Context context, Account account) throws AccountNotFoundException {
AccountManager ama = AccountManager.get(context);
String baseurl = ama.getUserData(account, Constants.KEY_OC_BASE_URL);
if (baseurl == null )
throw new AccountNotFoundException(account, "Account not found", null);
return baseurl;
}
public static class AccountNotFoundException extends AccountsException {
/** Generated - should be refreshed every time the class changes!! */
private static final long serialVersionUID = -1684392454798508693L;
private Account mFailedAccount;
public AccountNotFoundException(Account failedAccount, String message, Throwable cause) {
super(message, cause);
mFailedAccount = failedAccount;
}
public Account getFailedAccount() {
return mFailedAccount;
}
}
public static class Constants {
/**
* Value under this key should handle path to webdav php script. Will be
* removed and usage should be replaced by combining
* {@link com.owncloud.android.authentication.AuthenticatorActivity.KEY_OC_BASE_URL} and
* {@link com.owncloud.android.lib.resources.status.utils.OwnCloudVersion}
*
* @deprecated
*/
public static final String KEY_OC_URL = "oc_url";
/**
* Version should be 3 numbers separated by dot so it can be parsed by
* {@link com.owncloud.android.lib.resources.status.utils.OwnCloudVersion}
*/
public static final String KEY_OC_VERSION = "oc_version";
/**
* Base url should point to owncloud installation without trailing / ie:
* http://server/path or https://owncloud.server
*/
public static final String KEY_OC_BASE_URL = "oc_base_url";
/**
* Flag signaling if the ownCloud server can be accessed with OAuth2 access tokens.
*/
public static final String KEY_SUPPORTS_OAUTH2 = "oc_supports_oauth2";
/**
* Flag signaling if the ownCloud server can be accessed with session cookies from SAML-based web single-sign-on.
*/
public static final String KEY_SUPPORTS_SAML_WEB_SSO = "oc_supports_saml_web_sso";
/**
* Flag signaling if the ownCloud server supports Share API"
*/
public static final String KEY_SUPPORTS_SHARE_API = "oc_supports_share_api";
}
}
@@ -0,0 +1,296 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
* Copyright (C) 2012 Bartek Przybylski
*
* 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.network;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
//import java.security.Provider;
import java.security.cert.X509Certificate;
//import java.util.Enumeration;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
//import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import org.apache.commons.httpclient.ConnectTimeoutException;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.apache.http.conn.ssl.X509HostnameVerifier;
//import android.os.Build;
import android.util.Log;
/**
* AdvancedSSLProtocolSocketFactory allows to create SSL {@link Socket}s with
* a custom SSLContext and an optional Hostname Verifier.
*
* @author David A. Velasco
*/
public class AdvancedSslSocketFactory implements ProtocolSocketFactory {
private static final String TAG = AdvancedSslSocketFactory.class.getSimpleName();
private SSLContext mSslContext = null;
private AdvancedX509TrustManager mTrustManager = null;
private X509HostnameVerifier mHostnameVerifier = null;
public SSLContext getSslContext() {
return mSslContext;
}
/**
* Constructor for AdvancedSSLProtocolSocketFactory.
*/
public AdvancedSslSocketFactory(SSLContext sslContext, AdvancedX509TrustManager trustManager, X509HostnameVerifier hostnameVerifier) {
if (sslContext == null)
throw new IllegalArgumentException("AdvancedSslSocketFactory can not be created with a null SSLContext");
if (trustManager == null)
throw new IllegalArgumentException("AdvancedSslSocketFactory can not be created with a null Trust Manager");
mSslContext = sslContext;
mTrustManager = trustManager;
mHostnameVerifier = hostnameVerifier;
}
/**
* @see ProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int)
*/
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException, UnknownHostException {
Socket socket = mSslContext.getSocketFactory().createSocket(host, port, clientHost, clientPort);
verifyPeerIdentity(host, port, socket);
return socket;
}
/*
private void logSslInfo() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.FROYO) {
Log.v(TAG, "SUPPORTED SSL PARAMETERS");
logSslParameters(mSslContext.getSupportedSSLParameters());
Log.v(TAG, "DEFAULT SSL PARAMETERS");
logSslParameters(mSslContext.getDefaultSSLParameters());
Log.i(TAG, "CURRENT PARAMETERS");
Log.i(TAG, "Protocol: " + mSslContext.getProtocol());
}
Log.i(TAG, "PROVIDER");
logSecurityProvider(mSslContext.getProvider());
}
private void logSecurityProvider(Provider provider) {
Log.i(TAG, "name: " + provider.getName());
Log.i(TAG, "version: " + provider.getVersion());
Log.i(TAG, "info: " + provider.getInfo());
Enumeration<?> keys = provider.propertyNames();
String key;
while (keys.hasMoreElements()) {
key = (String) keys.nextElement();
Log.i(TAG, " property " + key + " : " + provider.getProperty(key));
}
}
private void logSslParameters(SSLParameters params) {
Log.v(TAG, "Cipher suites: ");
String [] elements = params.getCipherSuites();
for (int i=0; i<elements.length ; i++) {
Log.v(TAG, " " + elements[i]);
}
Log.v(TAG, "Protocols: ");
elements = params.getProtocols();
for (int i=0; i<elements.length ; i++) {
Log.v(TAG, " " + elements[i]);
}
}
*/
/**
* Attempts to get a new socket connection to the given host within the
* given time limit.
*
* @param host the host name/IP
* @param port the port on the host
* @param clientHost the local host name/IP to bind the socket to
* @param clientPort the port on the local machine
* @param params {@link HttpConnectionParams Http connection parameters}
*
* @return Socket a new socket
*
* @throws IOException if an I/O error occurs while creating the socket
* @throws UnknownHostException if the IP address of the host cannot be
* determined
*/
public Socket createSocket(final String host, final int port,
final InetAddress localAddress, final int localPort,
final HttpConnectionParams params) throws IOException,
UnknownHostException, ConnectTimeoutException {
Log.d(TAG, "Creating SSL Socket with remote " + host + ":" + port + ", local " + localAddress + ":" + localPort + ", params: " + params);
if (params == null) {
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
//logSslInfo();
SocketFactory socketfactory = mSslContext.getSocketFactory();
Log.d(TAG, " ... with connection timeout " + timeout + " and socket timeout " + params.getSoTimeout());
Socket socket = socketfactory.createSocket();
SocketAddress localaddr = new InetSocketAddress(localAddress, localPort);
SocketAddress remoteaddr = new InetSocketAddress(host, port);
socket.setSoTimeout(params.getSoTimeout());
socket.bind(localaddr);
ServerNameIndicator.setServerNameIndication(host, (SSLSocket)socket);
socket.connect(remoteaddr, timeout);
verifyPeerIdentity(host, port, socket);
return socket;
}
/**
* @see ProtocolSocketFactory#createSocket(java.lang.String,int)
*/
public Socket createSocket(String host, int port) throws IOException,
UnknownHostException {
Log.d(TAG, "Creating SSL Socket with remote " + host + ":" + port);
Socket socket = mSslContext.getSocketFactory().createSocket(host, port);
verifyPeerIdentity(host, port, socket);
return socket;
}
public boolean equals(Object obj) {
return ((obj != null) && obj.getClass().equals(
AdvancedSslSocketFactory.class));
}
public int hashCode() {
return AdvancedSslSocketFactory.class.hashCode();
}
public X509HostnameVerifier getHostNameVerifier() {
return mHostnameVerifier;
}
public void setHostNameVerifier(X509HostnameVerifier hostnameVerifier) {
mHostnameVerifier = hostnameVerifier;
}
/**
* Verifies the identity of the server.
*
* The server certificate is verified first.
*
* Then, the host name is compared with the content of the server certificate using the current host name verifier, if any.
* @param socket
*/
private void verifyPeerIdentity(String host, int port, Socket socket) throws IOException {
try {
CertificateCombinedException failInHandshake = null;
/// 1. VERIFY THE SERVER CERTIFICATE through the registered TrustManager (that should be an instance of AdvancedX509TrustManager)
try {
SSLSocket sock = (SSLSocket) socket; // a new SSLSession instance is created as a "side effect"
sock.startHandshake();
} catch (RuntimeException e) {
if (e instanceof CertificateCombinedException) {
failInHandshake = (CertificateCombinedException) e;
} else {
Throwable cause = e.getCause();
Throwable previousCause = null;
while (cause != null && cause != previousCause && !(cause instanceof CertificateCombinedException)) {
previousCause = cause;
cause = cause.getCause();
}
if (cause != null && cause instanceof CertificateCombinedException) {
failInHandshake = (CertificateCombinedException)cause;
}
}
if (failInHandshake == null) {
throw e;
}
failInHandshake.setHostInUrl(host);
}
/// 2. VERIFY HOSTNAME
SSLSession newSession = null;
boolean verifiedHostname = true;
if (mHostnameVerifier != null) {
if (failInHandshake != null) {
/// 2.1 : a new SSLSession instance was NOT created in the handshake
X509Certificate serverCert = failInHandshake.getServerCertificate();
try {
mHostnameVerifier.verify(host, serverCert);
} catch (SSLException e) {
verifiedHostname = false;
}
} else {
/// 2.2 : a new SSLSession instance was created in the handshake
newSession = ((SSLSocket)socket).getSession();
if (!mTrustManager.isKnownServer((X509Certificate)(newSession.getPeerCertificates()[0]))) {
verifiedHostname = mHostnameVerifier.verify(host, newSession);
}
}
}
/// 3. Combine the exceptions to throw, if any
if (!verifiedHostname) {
SSLPeerUnverifiedException pue = new SSLPeerUnverifiedException("Names in the server certificate do not match to " + host + " in the URL");
if (failInHandshake == null) {
failInHandshake = new CertificateCombinedException((X509Certificate) newSession.getPeerCertificates()[0]);
failInHandshake.setHostInUrl(host);
}
failInHandshake.setSslPeerUnverifiedException(pue);
pue.initCause(failInHandshake);
throw pue;
} else if (failInHandshake != null) {
SSLHandshakeException hse = new SSLHandshakeException("Server certificate could not be verified");
hse.initCause(failInHandshake);
throw hse;
}
} catch (IOException io) {
try {
socket.close();
} catch (Exception x) {
// NOTHING - irrelevant exception for the caller
}
throw io;
}
}
}
@@ -0,0 +1,155 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
*
* 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.network;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertStoreException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import android.util.Log;
/**
* @author David A. Velasco
*/
public class AdvancedX509TrustManager implements X509TrustManager {
private static final String TAG = AdvancedX509TrustManager.class.getSimpleName();
private X509TrustManager mStandardTrustManager = null;
private KeyStore mKnownServersKeyStore;
/**
* Constructor for AdvancedX509TrustManager
*
* @param knownServersCertStore Local certificates store with server certificates explicitly trusted by the user.
* @throws CertStoreException When no default X509TrustManager instance was found in the system.
*/
public AdvancedX509TrustManager(KeyStore knownServersKeyStore)
throws NoSuchAlgorithmException, KeyStoreException, CertStoreException {
super();
TrustManagerFactory factory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
factory.init((KeyStore)null);
mStandardTrustManager = findX509TrustManager(factory);
mKnownServersKeyStore = knownServersKeyStore;
}
/**
* Locates the first X509TrustManager provided by a given TrustManagerFactory
* @param factory TrustManagerFactory to inspect in the search for a X509TrustManager
* @return The first X509TrustManager found in factory.
* @throws CertStoreException When no X509TrustManager instance was found in factory
*/
private X509TrustManager findX509TrustManager(TrustManagerFactory factory) throws CertStoreException {
TrustManager tms[] = factory.getTrustManagers();
for (int i = 0; i < tms.length; i++) {
if (tms[i] instanceof X509TrustManager) {
return (X509TrustManager) tms[i];
}
}
return null;
}
/**
* @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[],
* String authType)
*/
public void checkClientTrusted(X509Certificate[] certificates, String authType) throws CertificateException {
mStandardTrustManager.checkClientTrusted(certificates, authType);
}
/**
* @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[],
* String authType)
*/
public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException {
if (!isKnownServer(certificates[0])) {
CertificateCombinedException result = new CertificateCombinedException(certificates[0]);
try {
certificates[0].checkValidity();
} catch (CertificateExpiredException c) {
result.setCertificateExpiredException(c);
} catch (CertificateNotYetValidException c) {
result.setCertificateNotYetException(c);
}
try {
mStandardTrustManager.checkServerTrusted(certificates, authType);
} catch (CertificateException c) {
Throwable cause = c.getCause();
Throwable previousCause = null;
while (cause != null && cause != previousCause && !(cause instanceof CertPathValidatorException)) { // getCause() is not funny
previousCause = cause;
cause = cause.getCause();
}
if (cause != null && cause instanceof CertPathValidatorException) {
result.setCertPathValidatorException((CertPathValidatorException)cause);
} else {
result.setOtherCertificateException(c);
}
}
if (result.isException())
throw result;
}
}
/**
* @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()
*/
public X509Certificate[] getAcceptedIssuers() {
return mStandardTrustManager.getAcceptedIssuers();
}
public boolean isKnownServer(X509Certificate cert) {
try {
return (mKnownServersKeyStore.getCertificateAlias(cert) != null);
} catch (KeyStoreException e) {
Log.d(TAG, "Fail while checking certificate in the known-servers store");
return false;
}
}
}
@@ -0,0 +1,278 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
*
* 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.network;
import java.util.Map;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.auth.AuthChallengeParser;
import org.apache.commons.httpclient.auth.AuthScheme;
import org.apache.commons.httpclient.auth.AuthenticationException;
import org.apache.commons.httpclient.auth.InvalidCredentialsException;
import org.apache.commons.httpclient.auth.MalformedChallengeException;
import android.util.Log;
/**
* Bearer authentication scheme as defined in RFC 6750.
*
* @author David A. Velasco
*/
public class BearerAuthScheme implements AuthScheme /*extends RFC2617Scheme*/ {
private static final String TAG = BearerAuthScheme.class.getSimpleName();
public static final String AUTH_POLICY = "Bearer";
/** Whether the bearer authentication process is complete */
private boolean mComplete;
/** Authentication parameter map */
@SuppressWarnings("rawtypes")
private Map mParams = null;
/**
* Default constructor for the bearer authentication scheme.
*/
public BearerAuthScheme() {
mComplete = false;
}
/**
* Constructor for the basic authentication scheme.
*
* @param challenge Authentication challenge
*
* @throws MalformedChallengeException Thrown if the authentication challenge is malformed
*
* @deprecated Use parameterless constructor and {@link AuthScheme#processChallenge(String)} method
*/
public BearerAuthScheme(final String challenge) throws MalformedChallengeException {
processChallenge(challenge);
mComplete = true;
}
/**
* Returns textual designation of the bearer authentication scheme.
*
* @return "Bearer"
*/
public String getSchemeName() {
return "bearer";
}
/**
* Processes the Bearer challenge.
*
* @param challenge The challenge string
*
* @throws MalformedChallengeException Thrown if the authentication challenge is malformed
*/
public void processChallenge(String challenge) throws MalformedChallengeException {
String s = AuthChallengeParser.extractScheme(challenge);
if (!s.equalsIgnoreCase(getSchemeName())) {
throw new MalformedChallengeException(
"Invalid " + getSchemeName() + " challenge: " + challenge);
}
mParams = AuthChallengeParser.extractParams(challenge);
mComplete = true;
}
/**
* Tests if the Bearer authentication process has been completed.
*
* @return 'true' if Bearer authorization has been processed, 'false' otherwise.
*/
public boolean isComplete() {
return this.mComplete;
}
/**
* Produces bearer authorization string for the given set of
* {@link Credentials}.
*
* @param credentials The set of credentials to be used for authentication
* @param method Method name is ignored by the bearer authentication scheme
* @param uri URI is ignored by the bearer authentication scheme
* @throws InvalidCredentialsException If authentication credentials are not valid or not applicable
* for this authentication scheme
* @throws AuthenticationException If authorization string cannot be generated due to an authentication failure
* @return A bearer authorization string
*
* @deprecated Use {@link #authenticate(Credentials, HttpMethod)}
*/
public String authenticate(Credentials credentials, String method, String uri) throws AuthenticationException {
Log.d(TAG, "enter BearerScheme.authenticate(Credentials, String, String)");
BearerCredentials bearer = null;
try {
bearer = (BearerCredentials) credentials;
} catch (ClassCastException e) {
throw new InvalidCredentialsException(
"Credentials cannot be used for bearer authentication: "
+ credentials.getClass().getName());
}
return BearerAuthScheme.authenticate(bearer);
}
/**
* Returns 'false'. Bearer authentication scheme is request based.
*
* @return 'false'.
*/
public boolean isConnectionBased() {
return false;
}
/**
* Produces bearer authorization string for the given set of {@link Credentials}.
*
* @param credentials The set of credentials to be used for authentication
* @param method The method being authenticated
* @throws InvalidCredentialsException If authentication credentials are not valid or not applicable for this authentication
* scheme.
* @throws AuthenticationException If authorization string cannot be generated due to an authentication failure.
*
* @return a basic authorization string
*/
public String authenticate(Credentials credentials, HttpMethod method) throws AuthenticationException {
Log.d(TAG, "enter BearerScheme.authenticate(Credentials, HttpMethod)");
if (method == null) {
throw new IllegalArgumentException("Method may not be null");
}
BearerCredentials bearer = null;
try {
bearer = (BearerCredentials) credentials;
} catch (ClassCastException e) {
throw new InvalidCredentialsException(
"Credentials cannot be used for bearer authentication: "
+ credentials.getClass().getName());
}
return BearerAuthScheme.authenticate(
bearer,
method.getParams().getCredentialCharset());
}
/**
* @deprecated Use {@link #authenticate(BearerCredentials, String)}
*
* Returns a bearer Authorization header value for the given
* {@link BearerCredentials}.
*
* @param credentials The credentials to encode.
*
* @return A bearer authorization string
*/
public static String authenticate(BearerCredentials credentials) {
return authenticate(credentials, "ISO-8859-1");
}
/**
* Returns a bearer Authorization header value for the given
* {@link BearerCredentials} and charset.
*
* @param credentials The credentials to encode.
* @param charset The charset to use for encoding the credentials
*
* @return A bearer authorization string
*
* @since 3.0
*/
public static String authenticate(BearerCredentials credentials, String charset) {
Log.d(TAG, "enter BearerAuthScheme.authenticate(BearerCredentials, String)");
if (credentials == null) {
throw new IllegalArgumentException("Credentials may not be null");
}
if (charset == null || charset.length() == 0) {
throw new IllegalArgumentException("charset may not be null or empty");
}
StringBuffer buffer = new StringBuffer();
buffer.append(credentials.getAccessToken());
//return "Bearer " + EncodingUtil.getAsciiString(EncodingUtil.getBytes(buffer.toString(), charset));
return "Bearer " + buffer.toString();
}
/**
* Returns a String identifying the authentication challenge. This is
* used, in combination with the host and port to determine if
* authorization has already been attempted or not. Schemes which
* require multiple requests to complete the authentication should
* return a different value for each stage in the request.
*
* Additionally, the ID should take into account any changes to the
* authentication challenge and return a different value when appropriate.
* For example when the realm changes in basic authentication it should be
* considered a different authentication attempt and a different value should
* be returned.
*
* This method simply returns the realm for the challenge.
*
* @return String a String identifying the authentication challenge.
*
* @deprecated no longer used
*/
@Override
public String getID() {
return getRealm();
}
/**
* Returns authentication parameter with the given name, if available.
*
* @param name The name of the parameter to be returned
*
* @return The parameter with the given name
*/
@Override
public String getParameter(String name) {
if (name == null) {
throw new IllegalArgumentException("Parameter name may not be null");
}
if (mParams == null) {
return null;
}
return (String) mParams.get(name.toLowerCase());
}
/**
* Returns authentication realm. The realm may not be null.
*
* @return The authentication realm
*/
@Override
public String getRealm() {
return getParameter("realm");
}
}
@@ -0,0 +1,104 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
*
* 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.network;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.util.LangUtils;
/**
* Bearer token {@link Credentials}
*
* @author David A. Velasco
*/
public class BearerCredentials implements Credentials {
private String mAccessToken;
/**
* The constructor with the bearer token
*
* @param token The bearer token
*/
public BearerCredentials(String token) {
/*if (token == null) {
throw new IllegalArgumentException("Bearer token may not be null");
}*/
mAccessToken = (token == null) ? "" : token;
}
/**
* Returns the access token
*
* @return The access token
*/
public String getAccessToken() {
return mAccessToken;
}
/**
* Get this object string.
*
* @return The access token
*/
public String toString() {
return mAccessToken;
}
/**
* Does a hash of the access token.
*
* @return The hash code of the access token
*/
public int hashCode() {
int hash = LangUtils.HASH_SEED;
hash = LangUtils.hashCode(hash, mAccessToken);
return hash;
}
/**
* These credentials are assumed equal if accessToken is the same.
*
* @param o The other object to compare with.
*
* @return 'True' if the object is equivalent.
*/
public boolean equals(Object o) {
if (o == null) return false;
if (this == o) return true;
if (this.getClass().equals(o.getClass())) {
BearerCredentials that = (BearerCredentials) o;
if (LangUtils.equals(mAccessToken, that.mAccessToken)) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,137 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
*
* 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.network;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLPeerUnverifiedException;
/**
* Exception joining all the problems that {@link AdvancedX509TrustManager} can find in
* a certificate chain for a server.
*
* This was initially created as an extension of CertificateException, but some
* implementations of the SSL socket layer in existing devices are REPLACING the CertificateException
* instances thrown by {@link javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[], String)}
* with SSLPeerUnverifiedException FORGETTING THE CAUSING EXCEPTION instead of wrapping it.
*
* Due to this, extending RuntimeException is necessary to get that the CertificateCombinedException
* instance reaches {@link AdvancedSslSocketFactory#verifyPeerIdentity}.
*
* BE CAREFUL. As a RuntimeException extensions, Java compilers do not require to handle it
* in client methods. Be sure to use it only when you know exactly where it will go.
*
* @author David A. Velasco
*/
public class CertificateCombinedException extends RuntimeException {
/** Generated - to refresh every time the class changes */
private static final long serialVersionUID = -8875782030758554999L;
private X509Certificate mServerCert = null;
private String mHostInUrl;
private CertificateExpiredException mCertificateExpiredException = null;
private CertificateNotYetValidException mCertificateNotYetValidException = null;
private CertPathValidatorException mCertPathValidatorException = null;
private CertificateException mOtherCertificateException = null;
private SSLPeerUnverifiedException mSslPeerUnverifiedException = null;
public CertificateCombinedException(X509Certificate x509Certificate) {
mServerCert = x509Certificate;
}
public X509Certificate getServerCertificate() {
return mServerCert;
}
public String getHostInUrl() {
return mHostInUrl;
}
public void setHostInUrl(String host) {
mHostInUrl = host;
}
public CertificateExpiredException getCertificateExpiredException() {
return mCertificateExpiredException;
}
public void setCertificateExpiredException(CertificateExpiredException c) {
mCertificateExpiredException = c;
}
public CertificateNotYetValidException getCertificateNotYetValidException() {
return mCertificateNotYetValidException;
}
public void setCertificateNotYetException(CertificateNotYetValidException c) {
mCertificateNotYetValidException = c;
}
public CertPathValidatorException getCertPathValidatorException() {
return mCertPathValidatorException;
}
public void setCertPathValidatorException(CertPathValidatorException c) {
mCertPathValidatorException = c;
}
public CertificateException getOtherCertificateException() {
return mOtherCertificateException;
}
public void setOtherCertificateException(CertificateException c) {
mOtherCertificateException = c;
}
public SSLPeerUnverifiedException getSslPeerUnverifiedException() {
return mSslPeerUnverifiedException ;
}
public void setSslPeerUnverifiedException(SSLPeerUnverifiedException s) {
mSslPeerUnverifiedException = s;
}
public boolean isException() {
return (mCertificateExpiredException != null ||
mCertificateNotYetValidException != null ||
mCertPathValidatorException != null ||
mOtherCertificateException != null ||
mSslPeerUnverifiedException != null);
}
public boolean isRecoverable() {
return (mCertificateExpiredException != null ||
mCertificateNotYetValidException != null ||
mCertPathValidatorException != null ||
mSslPeerUnverifiedException != null);
}
}
@@ -0,0 +1,151 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
*
* 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.network;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.apache.commons.httpclient.methods.RequestEntity;
import android.util.Log;
/**
* A RequestEntity that represents a PIECE of a file.
*
* @author David A. Velasco
*/
public class ChunkFromFileChannelRequestEntity implements RequestEntity, ProgressiveDataTransferer {
private static final String TAG = ChunkFromFileChannelRequestEntity.class.getSimpleName();
//private final File mFile;
private final FileChannel mChannel;
private final String mContentType;
private final long mChunkSize;
private final File mFile;
private long mOffset;
private long mTransferred;
Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>();
private ByteBuffer mBuffer = ByteBuffer.allocate(4096);
public ChunkFromFileChannelRequestEntity(final FileChannel channel, final String contentType, long chunkSize, final File file) {
super();
if (channel == null) {
throw new IllegalArgumentException("File may not be null");
}
if (chunkSize <= 0) {
throw new IllegalArgumentException("Chunk size must be greater than zero");
}
mChannel = channel;
mContentType = contentType;
mChunkSize = chunkSize;
mFile = file;
mOffset = 0;
mTransferred = 0;
}
public void setOffset(long offset) {
mOffset = offset;
}
public long getContentLength() {
try {
return Math.min(mChunkSize, mChannel.size() - mChannel.position());
} catch (IOException e) {
return mChunkSize;
}
}
public String getContentType() {
return mContentType;
}
public boolean isRepeatable() {
return true;
}
@Override
public void addDatatransferProgressListener(OnDatatransferProgressListener listener) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.add(listener);
}
}
@Override
public void addDatatransferProgressListeners(Collection<OnDatatransferProgressListener> listeners) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.addAll(listeners);
}
}
@Override
public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.remove(listener);
}
}
public void writeRequest(final OutputStream out) throws IOException {
int readCount = 0;
Iterator<OnDatatransferProgressListener> it = null;
try {
mChannel.position(mOffset);
long size = mFile.length();
if (size == 0) size = -1;
long maxCount = Math.min(mOffset + mChunkSize, mChannel.size());
while (mChannel.position() < maxCount) {
readCount = mChannel.read(mBuffer);
out.write(mBuffer.array(), 0, readCount);
mBuffer.clear();
if (mTransferred < maxCount) { // condition to avoid accumulate progress for repeated chunks
mTransferred += readCount;
}
synchronized (mDataTransferListeners) {
it = mDataTransferListeners.iterator();
while (it.hasNext()) {
it.next().onTransferProgress(readCount, mTransferred, size, mFile.getAbsolutePath());
}
}
}
} catch (IOException io) {
Log.e(TAG, io.getMessage());
throw new RuntimeException("Ugly solution to workaround the default policy of retries when the server falls while uploading ; temporal fix; really", io);
}
}
}
@@ -0,0 +1,138 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
* Copyright (C) 2012 Bartek Przybylski
*
* 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.network;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.apache.commons.httpclient.methods.RequestEntity;
import android.util.Log;
/**
* A RequestEntity that represents a File.
*
*/
public class FileRequestEntity implements RequestEntity, ProgressiveDataTransferer {
final File mFile;
final String mContentType;
Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>();
public FileRequestEntity(final File file, final String contentType) {
super();
this.mFile = file;
this.mContentType = contentType;
if (file == null) {
throw new IllegalArgumentException("File may not be null");
}
}
@Override
public long getContentLength() {
return mFile.length();
}
@Override
public String getContentType() {
return mContentType;
}
@Override
public boolean isRepeatable() {
return true;
}
@Override
public void addDatatransferProgressListener(OnDatatransferProgressListener listener) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.add(listener);
}
}
@Override
public void addDatatransferProgressListeners(Collection<OnDatatransferProgressListener> listeners) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.addAll(listeners);
}
}
@Override
public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
synchronized (mDataTransferListeners) {
mDataTransferListeners.remove(listener);
}
}
@Override
public void writeRequest(final OutputStream out) throws IOException {
//byte[] tmp = new byte[4096];
ByteBuffer tmp = ByteBuffer.allocate(4096);
int readResult = 0;
// TODO(bprzybylski): each mem allocation can throw OutOfMemoryError we need to handle it
// globally in some fashionable manner
RandomAccessFile raf = new RandomAccessFile(mFile, "r");
FileChannel channel = raf.getChannel();
Iterator<OnDatatransferProgressListener> it = null;
long transferred = 0;
long size = mFile.length();
if (size == 0) size = -1;
try {
while ((readResult = channel.read(tmp)) >= 0) {
out.write(tmp.array(), 0, readResult);
tmp.clear();
transferred += readResult;
synchronized (mDataTransferListeners) {
it = mDataTransferListeners.iterator();
while (it.hasNext()) {
it.next().onTransferProgress(readResult, transferred, size, mFile.getAbsolutePath());
}
}
}
} catch (IOException io) {
Log.e("FileRequestException", io.getMessage());
throw new RuntimeException("Ugly solution to workaround the default policy of retries when the server falls while uploading ; temporal fix; really", io);
} finally {
channel.close();
raf.close();
}
}
}
@@ -0,0 +1,176 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
*
* 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.network;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import android.content.Context;
import android.util.Log;
public class NetworkUtils {
final private static String TAG = NetworkUtils.class.getSimpleName();
/** Default timeout for waiting data from the server */
public static final int DEFAULT_DATA_TIMEOUT = 60000;
/** Default timeout for establishing a connection */
public static final int DEFAULT_CONNECTION_TIMEOUT = 60000;
/** Connection manager for all the OwnCloudClients */
private static MultiThreadedHttpConnectionManager mConnManager = null;
private static Protocol mDefaultHttpsProtocol = null;
private static AdvancedSslSocketFactory mAdvancedSslSocketFactory = null;
private static X509HostnameVerifier mHostnameVerifier = null;
/**
* Registers or unregisters the proper components for advanced SSL handling.
* @throws IOException
*/
public static void registerAdvancedSslContext(boolean register, Context context) throws GeneralSecurityException, IOException {
Protocol pr = null;
try {
pr = Protocol.getProtocol("https");
if (pr != null && mDefaultHttpsProtocol == null) {
mDefaultHttpsProtocol = pr;
}
} catch (IllegalStateException e) {
// nothing to do here; really
}
boolean isRegistered = (pr != null && pr.getSocketFactory() instanceof AdvancedSslSocketFactory);
if (register && !isRegistered) {
Protocol.registerProtocol("https", new Protocol("https", getAdvancedSslSocketFactory(context), 443));
} else if (!register && isRegistered) {
if (mDefaultHttpsProtocol != null) {
Protocol.registerProtocol("https", mDefaultHttpsProtocol);
}
}
}
public static AdvancedSslSocketFactory getAdvancedSslSocketFactory(Context context) throws GeneralSecurityException, IOException {
if (mAdvancedSslSocketFactory == null) {
KeyStore trustStore = getKnownServersStore(context);
AdvancedX509TrustManager trustMgr = new AdvancedX509TrustManager(trustStore);
TrustManager[] tms = new TrustManager[] { trustMgr };
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tms, null);
mHostnameVerifier = new BrowserCompatHostnameVerifier();
mAdvancedSslSocketFactory = new AdvancedSslSocketFactory(sslContext, trustMgr, mHostnameVerifier);
}
return mAdvancedSslSocketFactory;
}
private static String LOCAL_TRUSTSTORE_FILENAME = "knownServers.bks";
private static String LOCAL_TRUSTSTORE_PASSWORD = "password";
private static KeyStore mKnownServersStore = null;
/**
* Returns the local store of reliable server certificates, explicitly accepted by the user.
*
* Returns a KeyStore instance with empty content if the local store was never created.
*
* Loads the store from the storage environment if needed.
*
* @param context Android context where the operation is being performed.
* @return KeyStore instance with explicitly-accepted server certificates.
* @throws KeyStoreException When the KeyStore instance could not be created.
* @throws IOException When an existing local trust store could not be loaded.
* @throws NoSuchAlgorithmException When the existing local trust store was saved with an unsupported algorithm.
* @throws CertificateException When an exception occurred while loading the certificates from the local trust store.
*/
private static KeyStore getKnownServersStore(Context context) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
if (mKnownServersStore == null) {
//mKnownServersStore = KeyStore.getInstance("BKS");
mKnownServersStore = KeyStore.getInstance(KeyStore.getDefaultType());
File localTrustStoreFile = new File(context.getFilesDir(), LOCAL_TRUSTSTORE_FILENAME);
Log.d(TAG, "Searching known-servers store at " + localTrustStoreFile.getAbsolutePath());
if (localTrustStoreFile.exists()) {
InputStream in = new FileInputStream(localTrustStoreFile);
try {
mKnownServersStore.load(in, LOCAL_TRUSTSTORE_PASSWORD.toCharArray());
} finally {
in.close();
}
} else {
mKnownServersStore.load(null, LOCAL_TRUSTSTORE_PASSWORD.toCharArray()); // necessary to initialize an empty KeyStore instance
}
}
return mKnownServersStore;
}
public static void addCertToKnownServersStore(Certificate cert, Context context) throws KeyStoreException, NoSuchAlgorithmException,
CertificateException, IOException {
KeyStore knownServers = getKnownServersStore(context);
knownServers.setCertificateEntry(Integer.toString(cert.hashCode()), cert);
FileOutputStream fos = null;
try {
fos = context.openFileOutput(LOCAL_TRUSTSTORE_FILENAME, Context.MODE_PRIVATE);
knownServers.store(fos, LOCAL_TRUSTSTORE_PASSWORD.toCharArray());
} finally {
fos.close();
}
}
static public MultiThreadedHttpConnectionManager getMultiThreadedConnManager() {
if (mConnManager == null) {
mConnManager = new MultiThreadedHttpConnectionManager();
mConnManager.getParams().setDefaultMaxConnectionsPerHost(5);
mConnManager.getParams().setMaxTotalConnections(5);
}
return mConnManager;
}
}
@@ -0,0 +1,30 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
* Copyright (C) 2012 Bartek Przybylski
*
* 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.network;
public interface OnDatatransferProgressListener {
public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String fileAbsoluteName);
}
@@ -0,0 +1,39 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
*
* 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.network;
import java.util.Collection;
public interface ProgressiveDataTransferer {
public void addDatatransferProgressListener (OnDatatransferProgressListener listener);
public void addDatatransferProgressListeners(Collection<OnDatatransferProgressListener> listeners);
public void removeDatatransferProgressListener(OnDatatransferProgressListener listener);
}
@@ -0,0 +1,151 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
*
* 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.network;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLSocket;
import android.util.Log;
/**
* Enables the support of Server Name Indication if existing
* in the underlying network implementation.
*
* Build as a singleton.
*
* @author David A. Velasco
*/
public class ServerNameIndicator {
private static final String TAG = ServerNameIndicator.class.getSimpleName();
private static final AtomicReference<ServerNameIndicator> mSingleInstance = new AtomicReference<ServerNameIndicator>();
private static final String METHOD_NAME = "setHostname";
private final WeakReference<Class<?>> mSSLSocketClassRef;
private final WeakReference<Method> mSetHostnameMethodRef;
/**
* Private constructor, class is a singleton.
*
* @param sslSocketClass Underlying implementation class of {@link SSLSocket} used to connect with the server.
* @param setHostnameMethod Name of the method to call to enable the SNI support.
*/
private ServerNameIndicator(Class<?> sslSocketClass, Method setHostnameMethod) {
mSSLSocketClassRef = new WeakReference<Class<?>>(sslSocketClass);
mSetHostnameMethodRef = (setHostnameMethod == null) ? null : new WeakReference<Method>(setHostnameMethod);
}
/**
* Calls the {@code #setHostname(String)} method of the underlying implementation
* of {@link SSLSocket} if exists.
*
* Creates and initializes the single instance of the class when needed
*
* @param hostname The name of the server host of interest.
* @param sslSocket Client socket to connect with the server.
*/
public static void setServerNameIndication(String hostname, SSLSocket sslSocket) {
final Method setHostnameMethod = getMethod(sslSocket);
if (setHostnameMethod != null) {
try {
setHostnameMethod.invoke(sslSocket, hostname);
Log.i(TAG, "SNI done, hostname: " + hostname);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Call to SSLSocket#setHost(String) failed ", e);
} catch (IllegalAccessException e) {
Log.e(TAG, "Call to SSLSocket#setHost(String) failed ", e);
} catch (InvocationTargetException e) {
Log.e(TAG, "Call to SSLSocket#setHost(String) failed ", e);
}
} else {
Log.i(TAG, "SNI not supported");
}
}
/**
* Gets the method to invoke trying to minimize the effective
* application of reflection.
*
* @param sslSocket Instance of the SSL socket to use in connection with server.
* @return Method to call to indicate the server name of interest to the server.
*/
private static Method getMethod(SSLSocket sslSocket) {
final Class<?> sslSocketClass = sslSocket.getClass();
final ServerNameIndicator instance = mSingleInstance.get();
if (instance == null) {
return initFrom(sslSocketClass);
} else if (instance.mSSLSocketClassRef.get() != sslSocketClass) {
// the underlying class changed
return initFrom(sslSocketClass);
} else if (instance.mSetHostnameMethodRef == null) {
// SNI not supported
return null;
} else {
final Method cachedSetHostnameMethod = instance.mSetHostnameMethodRef.get();
return (cachedSetHostnameMethod == null) ? initFrom(sslSocketClass) : cachedSetHostnameMethod;
}
}
/**
* Singleton initializer.
*
* Uses reflection to extract and 'cache' the method to invoke to indicate the desited host name to the server side.
*
* @param sslSocketClass Underlying class providing the implementation of {@link SSLSocket}.
* @return Method to call to indicate the server name of interest to the server.
*/
private static Method initFrom(Class<?> sslSocketClass) {
Log.i(TAG, "SSLSocket implementation: " + sslSocketClass.getCanonicalName());
Method setHostnameMethod = null;
try {
setHostnameMethod = sslSocketClass.getMethod(METHOD_NAME, String.class);
} catch (SecurityException e) {
Log.e(TAG, "Could not access to SSLSocket#setHostname(String) method ", e);
} catch (NoSuchMethodException e) {
Log.i(TAG, "Could not find SSLSocket#setHostname(String) method - SNI not supported");
}
mSingleInstance.set(new ServerNameIndicator(sslSocketClass, setHostnameMethod));
return setHostnameMethod;
}
}
@@ -0,0 +1,159 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
*
* 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.network;
import java.util.Date;
import org.apache.jackrabbit.webdav.MultiStatusResponse;
import org.apache.jackrabbit.webdav.property.DavProperty;
import org.apache.jackrabbit.webdav.property.DavPropertyName;
import org.apache.jackrabbit.webdav.property.DavPropertySet;
import android.net.Uri;
import android.util.Log;
public class WebdavEntry {
private String mName, mPath, mUri, mContentType, mEtag;
private long mContentLength, mCreateTimestamp, mModifiedTimestamp;
public WebdavEntry(MultiStatusResponse ms, String splitElement) {
resetData();
if (ms.getStatus().length != 0) {
mUri = ms.getHref();
mPath = mUri.split(splitElement, 2)[1];
int status = ms.getStatus()[0].getStatusCode();
DavPropertySet propSet = ms.getProperties(status);
@SuppressWarnings("rawtypes")
DavProperty prop = propSet.get(DavPropertyName.DISPLAYNAME);
if (prop != null) {
mName = (String) prop.getName().toString();
mName = mName.substring(1, mName.length()-1);
}
else {
String[] tmp = mPath.split("/");
if (tmp.length > 0)
mName = tmp[tmp.length - 1];
}
// use unknown mimetype as default behavior
mContentType = "application/octet-stream";
prop = propSet.get(DavPropertyName.GETCONTENTTYPE);
if (prop != null) {
mContentType = (String) prop.getValue();
// dvelasco: some builds of ownCloud server 4.0.x added a trailing ';' to the MIME type ; if looks fixed, but let's be cautious
if (mContentType.indexOf(";") >= 0) {
mContentType = mContentType.substring(0, mContentType.indexOf(";"));
}
}
// check if it's a folder in the standard way: see RFC2518 12.2 . RFC4918 14.3
prop = propSet.get(DavPropertyName.RESOURCETYPE);
if (prop!= null) {
Object value = prop.getValue();
if (value != null) {
mContentType = "DIR"; // a specific attribute would be better, but this is enough; unless while we have no reason to distinguish MIME types for folders
}
}
prop = propSet.get(DavPropertyName.GETCONTENTLENGTH);
if (prop != null)
mContentLength = Long.parseLong((String) prop.getValue());
prop = propSet.get(DavPropertyName.GETLASTMODIFIED);
if (prop != null) {
Date d = WebdavUtils
.parseResponseDate((String) prop.getValue());
mModifiedTimestamp = (d != null) ? d.getTime() : 0;
}
prop = propSet.get(DavPropertyName.CREATIONDATE);
if (prop != null) {
Date d = WebdavUtils
.parseResponseDate((String) prop.getValue());
mCreateTimestamp = (d != null) ? d.getTime() : 0;
}
prop = propSet.get(DavPropertyName.GETETAG);
if (prop != null) {
mEtag = (String) prop.getValue();
mEtag = mEtag.substring(1, mEtag.length()-1);
}
} else {
Log.e("WebdavEntry",
"General fuckup, no status for webdav response");
}
}
public String path() {
return mPath;
}
public String decodedPath() {
return Uri.decode(mPath);
}
public String name() {
return mName;
}
public boolean isDirectory() {
return mContentType.equals("DIR");
}
public String contentType() {
return mContentType;
}
public String uri() {
return mUri;
}
public long contentLength() {
return mContentLength;
}
public long createTimestamp() {
return mCreateTimestamp;
}
public long modifiedTimestamp() {
return mModifiedTimestamp;
}
public String etag() {
return mEtag;
}
private void resetData() {
mName = mUri = mContentType = null;
mContentLength = mCreateTimestamp = mModifiedTimestamp = 0;
}
}
@@ -0,0 +1,83 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
* Copyright (C) 2012 Bartek Przybylski
*
* 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.network;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import android.net.Uri;
public class WebdavUtils {
public static final SimpleDateFormat DISPLAY_DATE_FORMAT = new SimpleDateFormat(
"dd.MM.yyyy hh:mm");
private static final SimpleDateFormat DATETIME_FORMATS[] = {
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US),
new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.sss'Z'", Locale.US),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US),
new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US),
new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) };
public static String prepareXmlForPropFind() {
String ret = "<?xml version=\"1.0\" ?><D:propfind xmlns:D=\"DAV:\"><D:allprop/></D:propfind>";
return ret;
}
public static String prepareXmlForPatch() {
return "<?xml version=\"1.0\" ?><D:propertyupdate xmlns:D=\"DAV:\"></D:propertyupdate>";
}
public static Date parseResponseDate(String date) {
Date returnDate = null;
for (int i = 0; i < DATETIME_FORMATS.length; ++i) {
try {
returnDate = DATETIME_FORMATS[i].parse(date);
return returnDate;
} catch (ParseException e) {
}
}
return null;
}
/**
* Encodes a path according to URI RFC 2396.
*
* If the received path doesn't start with "/", the method adds it.
*
* @param remoteFilePath Path
* @return Encoded path according to RFC 2396, always starting with "/"
*/
public static String encodePath(String remoteFilePath) {
String encodedPath = Uri.encode(remoteFilePath, "/");
if (!encodedPath.startsWith("/"))
encodedPath = "/" + encodedPath;
return encodedPath;
}
}
@@ -0,0 +1,33 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
* Copyright (C) 2012 Bartek Przybylski
*
* 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.operations;
public interface OnRemoteOperationListener {
void onRemoteOperationFinish(RemoteOperation caller, RemoteOperationResult result);
}
@@ -0,0 +1,35 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
* Copyright (C) 2012 Bartek Przybylski
*
* 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.operations;
public class OperationCancelledException extends Exception {
/**
* Generated serial version - to avoid Java warning
*/
private static final long serialVersionUID = -6350981497740424983L;
}
@@ -0,0 +1,272 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
*
* 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.operations;
import java.io.IOException;
import org.apache.commons.httpclient.Credentials;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudClientFactory;
import com.owncloud.android.lib.common.network.BearerCredentials;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountsException;
import android.app.Activity;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
/**
* Operation which execution involves one or several interactions with an ownCloud server.
*
* Provides methods to execute the operation both synchronously or asynchronously.
*
* @author David A. Velasco
*/
public abstract class RemoteOperation implements Runnable {
private static final String TAG = RemoteOperation.class.getSimpleName();
/** ownCloud account in the remote ownCloud server to operate */
private Account mAccount = null;
/** Android Application context */
private Context mContext = null;
/** Object to interact with the remote server */
private OwnCloudClient mClient = null;
/** Callback object to notify about the execution of the remote operation */
private OnRemoteOperationListener mListener = null;
/** Handler to the thread where mListener methods will be called */
private Handler mListenerHandler = null;
/** Activity */
private Activity mCallerActivity;
/**
* Abstract method to implement the operation in derived classes.
*/
protected abstract RemoteOperationResult run(OwnCloudClient client);
/**
* Synchronously executes the remote operation on the received ownCloud account.
*
* Do not call this method from the main thread.
*
* This method should be used whenever an ownCloud account is available, instead of {@link #execute(OwnCloudClient)}.
*
* @param account ownCloud account in remote ownCloud server to reach during the execution of the operation.
* @param context Android context for the component calling the method.
* @return Result of the operation.
*/
public RemoteOperationResult execute(Account account, Context context) {
if (account == null)
throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Account");
if (context == null)
throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Context");
mAccount = account;
mContext = context.getApplicationContext();
try {
mClient = OwnCloudClientFactory.createOwnCloudClient(mAccount, mContext);
} catch (Exception e) {
Log.e(TAG, "Error while trying to access to " + mAccount.name, e);
return new RemoteOperationResult(e);
}
return run(mClient);
}
/**
* Synchronously executes the remote operation
*
* Do not call this method from the main thread.
*
* @param client Client object to reach an ownCloud server during the execution of the operation.
* @return Result of the operation.
*/
public RemoteOperationResult execute(OwnCloudClient client) {
if (client == null)
throw new IllegalArgumentException("Trying to execute a remote operation with a NULL OwnCloudClient");
mClient = client;
return run(client);
}
/**
* Asynchronously executes the remote operation
*
* This method should be used whenever an ownCloud account is available, instead of {@link #execute(OwnCloudClient)}.
*
* @param account ownCloud account in remote ownCloud server to reach during the execution of the operation.
* @param context Android context for the component calling the method.
* @param listener Listener to be notified about the execution of the operation.
* @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called.
* @return Thread were the remote operation is executed.
*/
public Thread execute(Account account, Context context, OnRemoteOperationListener listener, Handler listenerHandler, Activity callerActivity) {
if (account == null)
throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Account");
if (context == null)
throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Context");
mAccount = account;
mContext = context.getApplicationContext();
mCallerActivity = callerActivity;
mClient = null; // the client instance will be created from mAccount and mContext in the runnerThread to create below
mListener = listener;
mListenerHandler = listenerHandler;
Thread runnerThread = new Thread(this);
runnerThread.start();
return runnerThread;
}
/**
* Asynchronously executes the remote operation
*
* @param client Client object to reach an ownCloud server during the execution of the operation.
* @param listener Listener to be notified about the execution of the operation.
* @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called.
* @return Thread were the remote operation is executed.
*/
public Thread execute(OwnCloudClient client, OnRemoteOperationListener listener, Handler listenerHandler) {
if (client == null) {
throw new IllegalArgumentException("Trying to execute a remote operation with a NULL OwnCloudClient");
}
mClient = client;
if (listener == null) {
throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a listener to notiy the result");
}
mListener = listener;
if (listenerHandler == null) {
throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a handler to the listener's thread");
}
mListenerHandler = listenerHandler;
Thread runnerThread = new Thread(this);
runnerThread.start();
return runnerThread;
}
/**
* Asynchronous execution of the operation
* started by {@link RemoteOperation#execute(OwnCloudClient, OnRemoteOperationListener, Handler)},
* and result posting.
*
* TODO refactor && clean the code; now it's a mess
*/
@Override
public final void run() {
RemoteOperationResult result = null;
boolean repeat = false;
do {
try{
if (mClient == null) {
if (mAccount != null && mContext != null) {
if (mCallerActivity != null) {
mClient = OwnCloudClientFactory.createOwnCloudClient(mAccount, mContext, mCallerActivity);
} else {
mClient = OwnCloudClientFactory.createOwnCloudClient(mAccount, mContext);
}
} else {
throw new IllegalStateException("Trying to run a remote operation asynchronously with no client instance or account");
}
}
} catch (IOException e) {
Log.e(TAG, "Error while trying to access to " + mAccount.name, new AccountsException("I/O exception while trying to authorize the account", e));
result = new RemoteOperationResult(e);
} catch (AccountsException e) {
Log.e(TAG, "Error while trying to access to " + mAccount.name, e);
result = new RemoteOperationResult(e);
}
if (result == null)
result = run(mClient);
repeat = false;
if (mCallerActivity != null && mAccount != null && mContext != null && !result.isSuccess() &&
// (result.getCode() == ResultCode.UNAUTHORIZED || (result.isTemporalRedirection() && result.isIdPRedirection()))) {
(result.getCode() == ResultCode.UNAUTHORIZED || result.isIdPRedirection())) {
/// possible fail due to lack of authorization in an operation performed in foreground
Credentials cred = mClient.getCredentials();
String ssoSessionCookie = mClient.getSsoSessionCookie();
if (cred != null || ssoSessionCookie != null) {
/// confirmed : unauthorized operation
AccountManager am = AccountManager.get(mContext);
boolean bearerAuthorization = (cred != null && cred instanceof BearerCredentials);
boolean samlBasedSsoAuthorization = (cred == null && ssoSessionCookie != null);
if (bearerAuthorization) {
am.invalidateAuthToken(mAccount.type, ((BearerCredentials)cred).getAccessToken());
} else if (samlBasedSsoAuthorization ) {
am.invalidateAuthToken(mAccount.type, ssoSessionCookie);
} else {
am.clearPassword(mAccount);
}
mClient = null;
repeat = true; // when repeated, the creation of a new OwnCloudClient after erasing the saved credentials will trigger the login activity
result = null;
}
}
} while (repeat);
final RemoteOperationResult resultToSend = result;
if (mListenerHandler != null && mListener != null) {
mListenerHandler.post(new Runnable() {
@Override
public void run() {
mListener.onRemoteOperationFinish(RemoteOperation.this, resultToSend);
}
});
}
}
/**
* Returns the current client instance to access the remote server.
*
* @return Current client instance to access the remote server.
*/
public final OwnCloudClient getClient() {
return mClient;
}
}
@@ -0,0 +1,363 @@
/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
* Copyright (C) 2012 Bartek Przybylski
*
* 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.operations;
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import javax.net.ssl.SSLException;
import org.apache.commons.httpclient.ConnectTimeoutException;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.jackrabbit.webdav.DavException;
import org.json.JSONException;
import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
import com.owncloud.android.lib.common.network.CertificateCombinedException;
import android.accounts.Account;
import android.accounts.AccountsException;
import android.util.Log;
/**
* The result of a remote operation required to an ownCloud server.
*
* Provides a common classification of remote operation results for all the
* application.
*
* @author David A. Velasco
*/
public class RemoteOperationResult implements Serializable {
/** Generated - should be refreshed every time the class changes!! */
private static final long serialVersionUID = -8257349554488668693L;
private static final String TAG = "RemoteOperationResult";
public enum ResultCode {
OK,
OK_SSL,
OK_NO_SSL,
UNHANDLED_HTTP_CODE,
UNAUTHORIZED,
FILE_NOT_FOUND,
INSTANCE_NOT_CONFIGURED,
UNKNOWN_ERROR,
WRONG_CONNECTION,
TIMEOUT,
INCORRECT_ADDRESS,
HOST_NOT_AVAILABLE,
NO_NETWORK_CONNECTION,
SSL_ERROR,
SSL_RECOVERABLE_PEER_UNVERIFIED,
BAD_OC_VERSION,
CANCELLED,
INVALID_LOCAL_FILE_NAME,
INVALID_OVERWRITE,
CONFLICT,
OAUTH2_ERROR,
SYNC_CONFLICT,
LOCAL_STORAGE_FULL,
LOCAL_STORAGE_NOT_MOVED,
LOCAL_STORAGE_NOT_COPIED,
OAUTH2_ERROR_ACCESS_DENIED,
QUOTA_EXCEEDED,
ACCOUNT_NOT_FOUND,
ACCOUNT_EXCEPTION,
ACCOUNT_NOT_NEW,
ACCOUNT_NOT_THE_SAME,
INVALID_CHARACTER_IN_NAME
}
private boolean mSuccess = false;
private int mHttpCode = -1;
private Exception mException = null;
private ResultCode mCode = ResultCode.UNKNOWN_ERROR;
private String mRedirectedLocation;
private ArrayList<Object> mData;
public RemoteOperationResult(ResultCode code) {
mCode = code;
mSuccess = (code == ResultCode.OK || code == ResultCode.OK_SSL || code == ResultCode.OK_NO_SSL);
mData = null;
}
private RemoteOperationResult(boolean success, int httpCode) {
mSuccess = success;
mHttpCode = httpCode;
if (success) {
mCode = ResultCode.OK;
} else if (httpCode > 0) {
switch (httpCode) {
case HttpStatus.SC_UNAUTHORIZED:
mCode = ResultCode.UNAUTHORIZED;
break;
case HttpStatus.SC_NOT_FOUND:
mCode = ResultCode.FILE_NOT_FOUND;
break;
case HttpStatus.SC_INTERNAL_SERVER_ERROR:
mCode = ResultCode.INSTANCE_NOT_CONFIGURED;
break;
case HttpStatus.SC_CONFLICT:
mCode = ResultCode.CONFLICT;
break;
case HttpStatus.SC_INSUFFICIENT_STORAGE:
mCode = ResultCode.QUOTA_EXCEEDED;
break;
default:
mCode = ResultCode.UNHANDLED_HTTP_CODE;
Log.d(TAG, "RemoteOperationResult has processed UNHANDLED_HTTP_CODE: " + httpCode);
}
}
}
public RemoteOperationResult(boolean success, int httpCode, Header[] headers) {
this(success, httpCode);
if (headers != null) {
Header current;
for (int i=0; i<headers.length; i++) {
current = headers[i];
if ("Location".equals(current.getName())) {
mRedirectedLocation = current.getValue();
break;
}
}
}
}
public RemoteOperationResult(Exception e) {
mException = e;
if (e instanceof OperationCancelledException) {
mCode = ResultCode.CANCELLED;
} else if (e instanceof SocketException) {
mCode = ResultCode.WRONG_CONNECTION;
} else if (e instanceof SocketTimeoutException) {
mCode = ResultCode.TIMEOUT;
} else if (e instanceof ConnectTimeoutException) {
mCode = ResultCode.TIMEOUT;
} else if (e instanceof MalformedURLException) {
mCode = ResultCode.INCORRECT_ADDRESS;
} else if (e instanceof UnknownHostException) {
mCode = ResultCode.HOST_NOT_AVAILABLE;
} else if (e instanceof AccountNotFoundException) {
mCode = ResultCode.ACCOUNT_NOT_FOUND;
} else if (e instanceof AccountsException) {
mCode = ResultCode.ACCOUNT_EXCEPTION;
} else if (e instanceof SSLException || e instanceof RuntimeException) {
CertificateCombinedException se = getCertificateCombinedException(e);
if (se != null) {
mException = se;
if (se.isRecoverable()) {
mCode = ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED;
}
} else if (e instanceof RuntimeException) {
mCode = ResultCode.HOST_NOT_AVAILABLE;
} else {
mCode = ResultCode.SSL_ERROR;
}
} else {
mCode = ResultCode.UNKNOWN_ERROR;
}
}
public void setData(ArrayList<Object> files){
mData = files;
}
public ArrayList<Object> getData(){
return mData;
}
public boolean isSuccess() {
return mSuccess;
}
public boolean isCancelled() {
return mCode == ResultCode.CANCELLED;
}
public int getHttpCode() {
return mHttpCode;
}
public ResultCode getCode() {
return mCode;
}
public Exception getException() {
return mException;
}
public boolean isSslRecoverableException() {
return mCode == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED;
}
private CertificateCombinedException getCertificateCombinedException(Exception e) {
CertificateCombinedException result = null;
if (e instanceof CertificateCombinedException) {
return (CertificateCombinedException) e;
}
Throwable cause = mException.getCause();
Throwable previousCause = null;
while (cause != null && cause != previousCause && !(cause instanceof CertificateCombinedException)) {
previousCause = cause;
cause = cause.getCause();
}
if (cause != null && cause instanceof CertificateCombinedException) {
result = (CertificateCombinedException) cause;
}
return result;
}
public String getLogMessage() {
if (mException != null) {
if (mException instanceof OperationCancelledException) {
return "Operation cancelled by the caller";
} else if (mException instanceof SocketException) {
return "Socket exception";
} else if (mException instanceof SocketTimeoutException) {
return "Socket timeout exception";
} else if (mException instanceof ConnectTimeoutException) {
return "Connect timeout exception";
} else if (mException instanceof MalformedURLException) {
return "Malformed URL exception";
} else if (mException instanceof UnknownHostException) {
return "Unknown host exception";
} else if (mException instanceof CertificateCombinedException) {
if (((CertificateCombinedException) mException).isRecoverable())
return "SSL recoverable exception";
else
return "SSL exception";
} else if (mException instanceof SSLException) {
return "SSL exception";
} else if (mException instanceof DavException) {
return "Unexpected WebDAV exception";
} else if (mException instanceof HttpException) {
return "HTTP violation";
} else if (mException instanceof IOException) {
return "Unrecovered transport exception";
} else if (mException instanceof AccountNotFoundException) {
Account failedAccount = ((AccountNotFoundException)mException).getFailedAccount();
return mException.getMessage() + " (" + (failedAccount != null ? failedAccount.name : "NULL") + ")";
} else if (mException instanceof AccountsException) {
return "Exception while using account";
} else if (mException instanceof JSONException) {
return "JSON exception";
} else {
return "Unexpected exception";
}
}
if (mCode == ResultCode.INSTANCE_NOT_CONFIGURED) {
return "The ownCloud server is not configured!";
} else if (mCode == ResultCode.NO_NETWORK_CONNECTION) {
return "No network connection";
} else if (mCode == ResultCode.BAD_OC_VERSION) {
return "No valid ownCloud version was found at the server";
} else if (mCode == ResultCode.LOCAL_STORAGE_FULL) {
return "Local storage full";
} else if (mCode == ResultCode.LOCAL_STORAGE_NOT_MOVED) {
return "Error while moving file to final directory";
} else if (mCode == ResultCode.ACCOUNT_NOT_NEW) {
return "Account already existing when creating a new one";
} else if (mCode == ResultCode.ACCOUNT_NOT_THE_SAME) {
return "Authenticated with a different account than the one updating";
} else if (mCode == ResultCode.INVALID_CHARACTER_IN_NAME) {
return "The file name contains an forbidden character";
}
return "Operation finished with HTTP status code " + mHttpCode + " (" + (isSuccess() ? "success" : "fail") + ")";
}
public boolean isServerFail() {
return (mHttpCode >= HttpStatus.SC_INTERNAL_SERVER_ERROR);
}
public boolean isException() {
return (mException != null);
}
public boolean isTemporalRedirection() {
return (mHttpCode == 302 || mHttpCode == 307);
}
public String getRedirectedLocation() {
return mRedirectedLocation;
}
public boolean isIdPRedirection() {
return (mRedirectedLocation != null &&
(mRedirectedLocation.toUpperCase().contains("SAML") ||
mRedirectedLocation.toLowerCase().contains("wayf")));
}
}