1 /* ownCloud Android client application 
   2  *   Copyright (C) 2011  Bartek Przybylski 
   4  *   This program is free software: you can redistribute it and/or modify 
   5  *   it under the terms of the GNU General Public License as published by 
   6  *   the Free Software Foundation, either version 3 of the License, or 
   7  *   (at your option) any later version. 
   9  *   This program is distributed in the hope that it will be useful, 
  10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
  11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  12  *   GNU General Public License for more details. 
  14  *   You should have received a copy of the GNU General Public License 
  15  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  18 package eu
.alefzero
.webdav
; 
  20 import java
.io
.BufferedInputStream
; 
  22 import java
.io
.FileOutputStream
; 
  23 import java
.io
.IOException
; 
  24 import java
.io
.InputStream
; 
  25 import java
.util
.ArrayList
; 
  26 import java
.util
.List
; 
  28 import org
.apache
.commons
.httpclient
.Credentials
; 
  29 import org
.apache
.commons
.httpclient
.HostConfiguration
; 
  30 import org
.apache
.commons
.httpclient
.HttpClient
; 
  31 import org
.apache
.commons
.httpclient
.HttpConnectionManager
; 
  32 import org
.apache
.commons
.httpclient
.HttpException
; 
  33 import org
.apache
.commons
.httpclient
.HttpMethod
; 
  34 import org
.apache
.commons
.httpclient
.HttpMethodBase
; 
  35 import org
.apache
.commons
.httpclient
.HttpState
; 
  36 import org
.apache
.commons
.httpclient
.HttpVersion
; 
  37 import org
.apache
.commons
.httpclient
.UsernamePasswordCredentials
; 
  38 import org
.apache
.commons
.httpclient
.auth
.AuthPolicy
; 
  39 import org
.apache
.commons
.httpclient
.auth
.AuthScope
; 
  40 import org
.apache
.commons
.httpclient
.methods
.GetMethod
; 
  41 import org
.apache
.commons
.httpclient
.methods
.HeadMethod
; 
  42 import org
.apache
.commons
.httpclient
.methods
.PutMethod
; 
  43 import org
.apache
.commons
.httpclient
.params
.HttpMethodParams
; 
  44 import org
.apache
.http
.HttpStatus
; 
  45 import org
.apache
.http
.params
.CoreProtocolPNames
; 
  46 import org
.apache
.jackrabbit
.webdav
.client
.methods
.DavMethod
; 
  47 import org
.apache
.jackrabbit
.webdav
.client
.methods
.DeleteMethod
; 
  49 import com
.owncloud
.android
.network
.BearerAuthScheme
; 
  50 import com
.owncloud
.android
.network
.BearerCredentials
; 
  52 import android
.net
.Uri
; 
  53 import android
.util
.Log
; 
  55 public class WebdavClient 
extends HttpClient 
{ 
  57     private Credentials mCredentials
; 
  58     final private static String TAG 
= "WebdavClient"; 
  59     private static final String USER_AGENT 
= "Android-ownCloud"; 
  61     private OnDatatransferProgressListener mDataTransferListener
; 
  62     static private byte[] sExhaustBuffer 
= new byte[1024]; 
  67     public WebdavClient(HttpConnectionManager connectionMgr
) { 
  69         Log
.d(TAG
, "Creating WebdavClient"); 
  70         getParams().setParameter(HttpMethodParams
.USER_AGENT
, USER_AGENT
); 
  71         getParams().setParameter(CoreProtocolPNames
.PROTOCOL_VERSION
, HttpVersion
.HTTP_1_1
); 
  74     public void setBearerCredentials(String accessToken
) { 
  75         AuthPolicy
.registerAuthScheme(BearerAuthScheme
.AUTH_POLICY
, BearerAuthScheme
.class); 
  77         List
<String
> authPrefs 
= new ArrayList
<String
>(1); 
  78         authPrefs
.add(BearerAuthScheme
.AUTH_POLICY
); 
  79         getParams().setParameter(AuthPolicy
.AUTH_SCHEME_PRIORITY
, authPrefs
);         
  81         mCredentials 
= new BearerCredentials(accessToken
); 
  82         getState().setCredentials(AuthScope
.ANY
, mCredentials
); 
  85     public void setBasicCredentials(String username
, String password
) { 
  86         List
<String
> authPrefs 
= new ArrayList
<String
>(1); 
  87         authPrefs
.add(AuthPolicy
.BASIC
); 
  88         getParams().setParameter(AuthPolicy
.AUTH_SCHEME_PRIORITY
, authPrefs
);         
  90         getParams().setAuthenticationPreemptive(true
); 
  91         mCredentials 
= new UsernamePasswordCredentials(username
, password
); 
  92         getState().setCredentials(AuthScope
.ANY
, mCredentials
); 
  97      * Downloads a file in remoteFilepath to the local targetPath. 
  99      * @param remoteFilepath    Path to the file in the remote server, URL DECODED.  
 100      * @param targetFile        Local path to save the downloaded file. 
 101      * @return                  'True' when the file is successfully downloaded. 
 103     public boolean downloadFile(String remoteFilePath
, File targetFile
) { 
 105         GetMethod get 
= new GetMethod(mUri
.toString() + WebdavUtils
.encodePath(remoteFilePath
)); 
 108             int status 
= executeMethod(get
); 
 109             if (status 
== HttpStatus
.SC_OK
) { 
 110                 targetFile
.createNewFile(); 
 111                 BufferedInputStream bis 
= new BufferedInputStream( 
 112                         get
.getResponseBodyAsStream()); 
 113                 FileOutputStream fos 
= new FileOutputStream(targetFile
); 
 115                 byte[] bytes 
= new byte[4096]; 
 117                 while ((readResult 
= bis
.read(bytes
)) != -1) { 
 118                     if (mDataTransferListener 
!= null
) 
 119                         mDataTransferListener
.onTransferProgress(readResult
); 
 120                     fos
.write(bytes
, 0, readResult
); 
 125                 exhaustResponse(get
.getResponseBodyAsStream()); 
 127             Log
.e(TAG
, "Download of " + remoteFilePath 
+ " to " + targetFile 
+ " finished with HTTP status " + status 
+ (!ret?
"(FAIL)":"")); 
 128         } catch (Exception e
) { 
 129             logException(e
, "dowloading " + remoteFilePath
); 
 132             if (!ret 
&& targetFile
.exists()) { 
 135             get
.releaseConnection();    // let the connection available for other methods 
 141      * Deletes a remote file via webdav 
 142      * @param remoteFilePath       Remote file path of the file to delete, in URL DECODED format. 
 145     public boolean deleteFile(String remoteFilePath
) { 
 147         DavMethod delete 
= new DeleteMethod(mUri
.toString() + WebdavUtils
.encodePath(remoteFilePath
)); 
 149             int status 
= executeMethod(delete
); 
 150             ret 
= (status 
== HttpStatus
.SC_OK 
|| status 
== HttpStatus
.SC_ACCEPTED 
|| status 
== HttpStatus
.SC_NO_CONTENT
); 
 151             exhaustResponse(delete
.getResponseBodyAsStream()); 
 153             Log
.e(TAG
, "DELETE of " + remoteFilePath 
+ " finished with HTTP status " + status 
+  (!ret?
"(FAIL)":"")); 
 155         } catch (Exception e
) { 
 156             logException(e
, "deleting " + remoteFilePath
); 
 159             delete
.releaseConnection();    // let the connection available for other methods 
 165     public void setDataTransferProgressListener(OnDatatransferProgressListener listener
) { 
 166         mDataTransferListener 
= listener
; 
 170      * Creates or update a file in the remote server with the contents of a local file. 
 172      * @param localFile         Path to the local file to upload. 
 173      * @param remoteTarget      Remote path to the file to create or update, URL DECODED 
 174      * @param contentType       MIME type of the file. 
 175      * @return                  Status HTTP code returned by the server. 
 176      * @throws IOException      When a transport error that could not be recovered occurred while uploading the file to the server. 
 177      * @throws HttpException    When a violation of the HTTP protocol occurred.  
 179     public int putFile(String localFile
, String remoteTarget
, String contentType
) throws HttpException
, IOException 
{ 
 181         PutMethod put 
= new PutMethod(mUri
.toString() + WebdavUtils
.encodePath(remoteTarget
)); 
 184             File f 
= new File(localFile
); 
 185             FileRequestEntity entity 
= new FileRequestEntity(f
, contentType
); 
 186             entity
.addOnDatatransferProgressListener(mDataTransferListener
); 
 187             put
.setRequestEntity(entity
); 
 188             status 
= executeMethod(put
); 
 190             exhaustResponse(put
.getResponseBodyAsStream()); 
 193             put
.releaseConnection();    // let the connection available for other methods 
 199      * Tries to log in to the current URI, with the current credentials 
 201      * @return A {@link HttpStatus}-Code of the result. SC_OK is good. 
 203     public int tryToLogin() { 
 205         HeadMethod head 
= new HeadMethod(mUri
.toString()); 
 207             status 
= executeMethod(head
); 
 208             boolean result 
= status 
== HttpStatus
.SC_OK
; 
 209             Log
.d(TAG
, "HEAD for " + mUri 
+ " finished with HTTP status " + status 
+ (!result?
"(FAIL)":"")); 
 210             exhaustResponse(head
.getResponseBodyAsStream()); 
 212         } catch (Exception e
) { 
 213             logException(e
, "trying to login at " + mUri
.toString()); 
 216             head
.releaseConnection(); 
 223      * Check if a file exists in the OC server 
 225      * @return              'true' if the file exists; 'false' it doesn't exist 
 226      * @throws  Exception   When the existence could not be determined 
 228     public boolean existsFile(String path
) throws IOException
, HttpException 
{ 
 229         HeadMethod head 
= new HeadMethod(mUri
.toString() + WebdavUtils
.encodePath(path
)); 
 231             int status 
= executeMethod(head
); 
 232             Log
.d(TAG
, "HEAD to " + path 
+ " finished with HTTP status " + status 
+ ((status 
!= HttpStatus
.SC_OK
)?
"(FAIL)":"")); 
 233             exhaustResponse(head
.getResponseBodyAsStream()); 
 234             return (status 
== HttpStatus
.SC_OK
); 
 237             head
.releaseConnection();    // let the connection available for other methods 
 243      * Requests the received method with the received timeout (milliseconds). 
 245      * Executes the method through the inherited HttpClient.executedMethod(method). 
 247      * Sets the socket and connection timeouts only for the method received. 
 249      * The timeouts are both in milliseconds; 0 means 'infinite'; < 0 means 'do not change the default' 
 251      * @param method            HTTP method request. 
 252      * @param readTimeout       Timeout to set for data reception 
 253      * @param conntionTimout    Timeout to set for connection establishment 
 255     public int executeMethod(HttpMethodBase method
, int readTimeout
, int connectionTimeout
) throws HttpException
, IOException 
{ 
 256         int oldSoTimeout 
= getParams().getSoTimeout(); 
 257         int oldConnectionTimeout 
= getHttpConnectionManager().getParams().getConnectionTimeout(); 
 259             if (readTimeout 
>= 0) {  
 260                 method
.getParams().setSoTimeout(readTimeout
);   // this should be enough... 
 261                 getParams().setSoTimeout(readTimeout
);          // ... but this looks like necessary for HTTPS 
 263             if (connectionTimeout 
>= 0) { 
 264                 getHttpConnectionManager().getParams().setConnectionTimeout(connectionTimeout
); 
 266             return executeMethod(method
); 
 268             getParams().setSoTimeout(oldSoTimeout
); 
 269             getHttpConnectionManager().getParams().setConnectionTimeout(oldConnectionTimeout
); 
 274      * Exhausts a not interesting HTTP response. Encouraged by HttpClient documentation. 
 276      * @param responseBodyAsStream      InputStream with the HTTP response to exhaust. 
 278     public void exhaustResponse(InputStream responseBodyAsStream
) { 
 279         if (responseBodyAsStream 
!= null
) { 
 281                 while (responseBodyAsStream
.read(sExhaustBuffer
) >= 0); 
 282                 responseBodyAsStream
.close(); 
 284             } catch (IOException io
) { 
 285                 Log
.e(TAG
, "Unexpected exception while exhausting not interesting HTTP response; will be IGNORED", io
); 
 292      * Logs an exception triggered in a HTTP request.  
 294      * @param e         Caught exception. 
 295      * @param doing     Suffix to add at the end of the logged message. 
 297     private void logException(Exception e
, String doing
) { 
 298         if (e 
instanceof HttpException
) { 
 299             Log
.e(TAG
, "HTTP violation while " + doing
, e
); 
 301         } else if (e 
instanceof IOException
) { 
 302             Log
.e(TAG
, "Unrecovered transport exception while " + doing
, e
); 
 305             Log
.e(TAG
, "Unexpected exception while " + doing
, e
); 
 311      * Sets the connection and wait-for-data timeouts to be applied by default to the methods performed by this client. 
 313     public void setDefaultTimeouts(int defaultDataTimeout
, int defaultConnectionTimeout
) { 
 314             getParams().setSoTimeout(defaultDataTimeout
); 
 315             getHttpConnectionManager().getParams().setConnectionTimeout(defaultConnectionTimeout
); 
 319      * Sets the base URI for the helper methods that receive paths as parameters, instead of full URLs 
 322     public void setBaseUri(Uri uri
) { 
 326     public Uri 
getBaseUri() { 
 332     public int executeMethod(HostConfiguration hostconfig
, final HttpMethod method
, final HttpState state
) throws IOException
, HttpException  
{ 
 333         if (mCredentials 
instanceof BearerCredentials
) { 
 334             method
.getHostAuthState().setAuthScheme(AuthPolicy
.getAuthScheme(BearerAuthScheme
.AUTH_POLICY
)); 
 335             method
.getHostAuthState().setAuthAttempted(true
); 
 337         return super.executeMethod(hostconfig
, method
, state
); 
 341     public final Credentials 
getCredentials() {