diff --git a/src/WebDavClient/HttpRequestBuilder.cs b/src/WebDavClient/HttpRequestBuilder.cs
new file mode 100644
index 0000000..b455eca
--- /dev/null
+++ b/src/WebDavClient/HttpRequestBuilder.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Net;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+
+
+namespace Cadenza.Net
+{
+ public class HttpRequestBuilder
+ {
+ public String User { get; set; }
+ public String Password { get; set; }
+
+ public bool ForceSSL { get; set; } = true;
+
+ ///
+ /// Cookies used for shiboleth or other "session based" auth
+ ///
+ public CookieContainer cookies { get; set; } = null;
+ ///
+ /// Bearer token is used for oauth and similar
+ ///
+ public String bearerToken { get; set; } = null;
+
+ ///
+ /// Set the proxy to use:
+ /// - System: user system proxy
+ /// - Direct: no proxy (direct connection)
+ /// - other: use the url as proxy (i.e. http://proxy:6138 )
+ /// only http proxy are supported
+ ///
+ public String proxy { get; set; } = "System";
+ public String proxyUser { get; set; }
+ public String proxyPassword { get; set; }
+
+ ///
+ /// This will create a web request object for a given uri with the builder parameter
+ ///
+ ///
+ ///
+ public HttpWebRequest Build(String uri)
+ {
+ return Build(new Uri(uri));
+ }
+ ///
+ /// This will create a web request object for a given uri with the builder parameter
+ ///
+ ///
+ ///
+ public HttpWebRequest Build(Uri uri)
+ {
+ HttpWebRequest httpWebRequest = (HttpWebRequest)System.Net.HttpWebRequest.Create(uri);
+
+ // If you want to disable SSL certificate validation
+
+ if (ForceSSL)
+ {
+ System.Net.ServicePointManager.ServerCertificateValidationCallback +=
+ delegate (object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors sslError)
+ {
+ return true;
+ };
+ }
+
+ if (proxy == "Direct")
+ {
+ // no proxy
+ }
+ else if (proxy == "System" || String.IsNullOrEmpty(proxy))
+ {
+ // use system (IE) web proxy
+ httpWebRequest.Proxy = WebRequest.GetSystemWebProxy();
+ }
+ else
+ {
+ // custom proxy
+ WebProxy wproxy = new WebProxy(proxy, true);
+ if (!String.IsNullOrEmpty(proxyUser))
+ wproxy.Credentials = new NetworkCredential(proxyUser, proxyPassword);
+ httpWebRequest.Proxy = wproxy;
+ }
+
+ // The server may use authentication
+ if(cookies != null)
+ {
+ httpWebRequest.CookieContainer = cookies;
+ } else if(!String.IsNullOrEmpty(bearerToken))
+ {
+ httpWebRequest.Headers.Add("Authorization", "Bearer " + bearerToken);
+ }
+ else if (!String.IsNullOrEmpty(User) && !String.IsNullOrEmpty(Password))
+ {
+ NetworkCredential networkCredential;
+ networkCredential = new NetworkCredential(User, Password);
+ httpWebRequest.Credentials = networkCredential;
+ // Send authentication along with first request.
+ httpWebRequest.PreAuthenticate = true;
+ }
+
+ // our user agent
+ httpWebRequest.UserAgent = "Mozilla/5.0 (epiK) mirall/2.4.0 (build 1234)";
+ return httpWebRequest;
+ }
+ }
+}
diff --git a/src/WebDavClient/WebDAVClient.cs b/src/WebDavClient/WebDAVClient.cs
index 9c3a924..9a77c6b 100644
--- a/src/WebDavClient/WebDAVClient.cs
+++ b/src/WebDavClient/WebDAVClient.cs
@@ -1,14 +1,14 @@
/*
- * (C) 2010 Kees van den Broek: kvdb@kvdb.net
- * D-centralize: d-centralize.nl
- *
- * Latest van den Broek version and examples on: http://kvdb.net/projects/webdav
- *
- * Feel free to use this code however you like.
- * http://creativecommons.org/license/zero/
- *
- * Copyright (C) 2012 Xamarin Inc. (http://xamarin.com)
- */
+* (C) 2010 Kees van den Broek: kvdb@kvdb.net
+* D-centralize: d-centralize.nl
+*
+* Latest van den Broek version and examples on: http://kvdb.net/projects/webdav
+*
+* Feel free to use this code however you like.
+* http://creativecommons.org/license/zero/
+*
+* Copyright (C) 2012 Xamarin Inc. (http://xamarin.com)
+*/
using System;
using System.Collections.Generic;
@@ -17,44 +17,46 @@
using System.Text;
using System.Threading.Tasks;
using System.Xml;
-/*
-// If you want to disable SSL certificate validation
-using System.Net.Security;
-using System.Security.Cryptography.X509Certificates;
-*/
namespace Cadenza.Net
{
- public enum WebDavEntryType {
- Default,
- Directory,
- File,
- }
-
- public class WebDavEntry {
-
- public string Directory {get; internal set;}
- public string Name {get; internal set;}
- public string Path {get; internal set;}
- public WebDavEntryType Type {get; internal set;}
-
- internal WebDavEntry ()
- {
- }
-
- public override string ToString ()
- {
- return Path;
- }
- }
-
- public class WebDavClient
+ public enum WebDavEntryType {
+ Default,
+ Directory,
+ File,
+ }
+
+ public class WebDavEntry {
+
+ public string Directory {get; internal set;}
+ public string Name {get; internal set;}
+ public string Path {get; internal set;}
+ public string AbsoluteUri { get; internal set; }
+ public WebDavEntryType Type {get; internal set;}
+ public long? ContentLength { get; internal set; }
+
+ internal WebDavEntry ()
+ {
+ }
+
+ public override string ToString ()
+ {
+ return Path;
+ }
+ }
+
+ public class WebDavClient : IDisposable
{
//XXX: submit along with state object.
HttpWebRequest httpWebRequest;
#region WebDAV connection parameters
+ ///
+ /// Request builder to handle authentication, proxy and so on
+ ///
+ public HttpRequestBuilder requestBuilder { get; set; }
+
private String server;
///
/// Specify the WebDAV hostname (required).
@@ -90,46 +92,22 @@ public int? Port
get { return port; }
set { port = value; }
}
- private String user;
- ///
- /// Specify a username (optional)
- ///
- public String User
- {
- get { return user; }
- set { user = value; }
- }
- private String pass;
- ///
- /// Specify a password (optional)
- ///
- public String Pass
- {
- get { return pass; }
- set { pass = value; }
- }
- private String domain = null;
- public String Domain
- {
- get { return domain; }
- set { domain = value; }
- }
Uri getServerUrl(String path, Boolean appendTrailingSlash)
{
String completePath = basePath;
if (path != null)
{
- completePath += path.Trim('/');
+ completePath += path.Trim('/');
}
if (appendTrailingSlash && completePath.EndsWith("/") == false) { completePath += '/'; }
if(port.HasValue) {
- return new Uri(server + ":" + port + completePath);
+ return new Uri(server + ":" + port + completePath);
}
else {
- return new Uri(server + completePath);
+ return new Uri(server + completePath);
}
}
@@ -149,7 +127,7 @@ public Task> List()
/// List files in the given directory
///
///
- public Task> List(String path)
+ public Task> List(String path)
{
// Set default depth to 1. This would prevent recursion.
return List(path, 1);
@@ -161,7 +139,7 @@ public Task> List(String path)
/// List only files in this path
/// Recursion depth
/// A list of files (entries without a trailing slash) and directories (entries with a trailing slash)
- public Task> List(String remoteFilePath, int? depth)
+ public Task> List(String remoteFilePath, int? depth)
{
// Uri should end with a trailing slash
Uri listUri = getServerUrl(remoteFilePath, true);
@@ -180,13 +158,17 @@ public Task> List(String remoteFilePath, int? depth)
headers.Add("Depth", depth.ToString());
}
- return WebDavOperation(listUri, "PROPFIND", headers, Encoding.UTF8.GetBytes(propfind.ToString()), null, FinishList, remoteFilePath);
+ return WebDavOperation(listUri, "PROPFIND", headers, Encoding.UTF8.GetBytes(propfind.ToString()), null, FinishList, remoteFilePath);
}
+ private String NormalizePath(String path)
+ {
+ return path.Trim('/');
+ }
IEnumerable FinishList(IAsyncResult result)
{
- string remoteFilePath = (string)result.AsyncState;
+ string remoteFilePath = NormalizePath((string)result.AsyncState);
using (HttpWebResponse response = (HttpWebResponse)httpWebRequest.EndGetResponse(result))
{
@@ -201,21 +183,56 @@ IEnumerable FinishList(IAsyncResult result)
{
XmlNode xmlNode = node.SelectSingleNode("d:href", xmlNsManager);
string filepath = Uri.UnescapeDataString(xmlNode.InnerXml);
- if (filepath.StartsWith (basePath))
- filepath = filepath.Substring (basePath.Length);
- if (filepath.Length == 0 || filepath == remoteFilePath)
- continue;
- var type = filepath.EndsWith ("/") ? WebDavEntryType.Directory : WebDavEntryType.File;
- int endDir = filepath.LastIndexOf ('/');
- if (type == WebDavEntryType.Directory)
- endDir = filepath.LastIndexOf ("/", endDir - 1);
- endDir++;
- yield return new WebDavEntry {
- Directory = filepath.Substring (0, endDir),
- Name = filepath.Substring (endDir),
- Path = filepath,
- Type = type,
- };
+ string uri = filepath;
+ if (filepath.StartsWith (basePath))
+ filepath = filepath.Substring (basePath.Length);
+
+ // skip the "query" node
+ if (filepath.Length == 0 || NormalizePath(filepath) == remoteFilePath)
+ continue;
+ var type = filepath.EndsWith ("/") ? WebDavEntryType.Directory : WebDavEntryType.File;
+ int endDir = filepath.LastIndexOf ('/');
+ if (type == WebDavEntryType.Directory)
+ endDir = filepath.LastIndexOf ("/", endDir - 1);
+ endDir++;
+
+ long? contentLength = null;
+
+ XmlNode propStatNode = node.SelectSingleNode("d:propstat", xmlNsManager);
+ if(propStatNode != null)
+ {
+ XmlNode propNode = propStatNode.SelectSingleNode("d:prop", xmlNsManager);
+ if (propNode != null)
+ {
+ // get content length
+ XmlNode prop = propNode.SelectSingleNode("d:getcontentlength", xmlNsManager);
+ if (prop != null)
+ {
+ contentLength = long.Parse(prop.InnerText);
+ }
+
+ // get content type
+ prop = propNode.SelectSingleNode("d:resourcetype", xmlNsManager);
+ if (prop != null)
+ {
+ if (prop.SelectSingleNode("d:collection", xmlNsManager) != null)
+ type = WebDavEntryType.Directory;
+ else
+ type = WebDavEntryType.File;
+ }
+ }
+ }
+ // get name and get rid of trailing /
+ var name = filepath.Substring(endDir).Trim('/');
+
+ yield return new WebDavEntry {
+ Directory = filepath.Substring(0, endDir),
+ Name = name,
+ Path = filepath,
+ Type = type,
+ ContentLength = contentLength,
+ AbsoluteUri = uri
+ };
}
}
}
@@ -226,7 +243,7 @@ IEnumerable FinishList(IAsyncResult result)
///
/// Local path and filename of the file to upload
/// Destination path and filename of the file on the server
- public Task Upload(String localFilePath, String remoteFilePath)
+ public Task Upload(String localFilePath, String remoteFilePath)
{
return Upload(localFilePath, remoteFilePath, null);
}
@@ -245,14 +262,12 @@ public Task Upload(String localFilePath, String remoteFilePath,
Uri uploadUri = getServerUrl(remoteFilePath, false);
string method = WebRequestMethods.Http.Put.ToString();
- return WebDavOperation(uploadUri, method, null, null, localFilePath, FinishUpload, state);
+ return WebDavOperation(uploadUri, method, null, null, localFilePath, FinishUpload, state);
}
HttpStatusCode FinishUpload(IAsyncResult result)
{
- int statusCode = 0;
-
using (HttpWebResponse response = (HttpWebResponse)httpWebRequest.EndGetResponse(result))
{
return response.StatusCode;
@@ -271,7 +286,7 @@ public Task Download(String remoteFilePath, String localFilePath
Uri downloadUri = getServerUrl(remoteFilePath, false);
string method = WebRequestMethods.Http.Get.ToString();
- return WebDavOperation(downloadUri, method, null, null, null, FinishDownload, localFilePath);
+ return WebDavOperation(downloadUri, method, null, null, null, FinishDownload, localFilePath);
}
@@ -282,7 +297,7 @@ HttpStatusCode FinishDownload(IAsyncResult result)
using (HttpWebResponse response = (HttpWebResponse)httpWebRequest.EndGetResponse(result))
{
int contentLength = int.Parse(response.GetResponseHeader("Content-Length"));
- int read = 0;
+ int read = 0;
using (Stream s = response.GetResponseStream())
{
using (FileStream fs = new FileStream(localFilePath, FileMode.Create, FileAccess.Write))
@@ -293,13 +308,13 @@ HttpStatusCode FinishDownload(IAsyncResult result)
{
bytesRead = s.Read(content, 0, content.Length);
fs.Write(content, 0, bytesRead);
- read += bytesRead;
+ read += bytesRead;
} while (bytesRead > 0);
}
}
- if (contentLength != read)
- Console.WriteLine ("Length read doesn't match header! Content-Length={0}; Read={1}", contentLength, read);
- return response.StatusCode;
+ if (contentLength != read)
+ Console.WriteLine ("Length read doesn't match header! Content-Length={0}; Read={1}", contentLength, read);
+ return response.StatusCode;
}
}
@@ -318,14 +333,41 @@ public Task CreateDir(string remotePath)
return WebDavOperation (dirUri, method, null, null, null, FinishCreateDir, null);
}
+ public Task Exists(string remotePath)
+ {
+ string method = WebRequestMethods.Http.Head.ToString();
+ Uri dirUri = getServerUrl(remotePath, false);
+ return WebDavOperation(dirUri, method, null, null, null, (result) =>
+ {
+ try
+ {
+ using (HttpWebResponse response = (HttpWebResponse)httpWebRequest.EndGetResponse(result))
+ {
+ return response.StatusCode == HttpStatusCode.OK;
+ }
+ }
+ catch (WebException wex)
+ {
+ var resp = (HttpWebResponse)wex.Response;
+ if (resp != null && resp.StatusCode == HttpStatusCode.NotFound)
+ return false;
+ throw wex;
+ }
+ }, null);
+ }
HttpStatusCode FinishCreateDir(IAsyncResult result)
{
- int statusCode = 0;
-
- using (HttpWebResponse response = (HttpWebResponse)httpWebRequest.EndGetResponse(result))
+ try
{
- return response.StatusCode;
+ using (HttpWebResponse response = (HttpWebResponse)httpWebRequest.EndGetResponse(result))
+ {
+ return response.StatusCode;
+ }
+ } catch (WebException wex)
+ {
+ var webresponse = ((WebException)wex).Response as HttpWebResponse;
+ return webresponse.StatusCode;
}
}
@@ -338,14 +380,12 @@ public Task Delete(string remoteFilePath)
{
Uri delUri = getServerUrl(remoteFilePath, remoteFilePath.EndsWith("/"));
- return WebDavOperation(delUri, "DELETE", null, null, null, FinishDelete, null);
+ return WebDavOperation(delUri, "DELETE", null, null, null, FinishDelete, null);
}
HttpStatusCode FinishDelete(IAsyncResult result)
{
- int statusCode = 0;
-
using (HttpWebResponse response = (HttpWebResponse)httpWebRequest.EndGetResponse(result))
{
return response.StatusCode;
@@ -367,6 +407,12 @@ class RequestState
public string uploadFilePath;
}
+ Task WebDavOperation(Uri uri, string requestMethod, IDictionary headers, byte[] content, string uploadFilePath, Func callback, object state)
+ {
+ return WebDavOperation(uri, requestMethod, headers, content, null, uploadFilePath, callback, state);
+ }
+
+
///
/// Perform the WebDAV call and fire the callback when finished.
///
@@ -377,42 +423,16 @@ class RequestState
///
///
///
- Task WebDavOperation(Uri uri, string requestMethod, IDictionary headers, byte[] content, string uploadFilePath, Func callback, object state)
+ Task WebDavOperation(Uri uri, string requestMethod, IDictionary headers, byte[] content, String contentType, string uploadFilePath, Func callback, object state)
{
- httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(uri);
+ httpWebRequest = requestBuilder.Build(uri);
/*
- * The following line fixes an authentication problem explained here:
- * http://www.devnewsgroups.net/dotnetframework/t9525-http-protocol-violation-long.aspx
- */
- System.Net.ServicePointManager.Expect100Continue = false;
-
- // If you want to disable SSL certificate validation
- /*
- System.Net.ServicePointManager.ServerCertificateValidationCallback +=
- delegate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors sslError)
- {
- bool validationResult = true;
- return validationResult;
- };
+ * The following line fixes an authentication problem explained here:
+ * http://www.devnewsgroups.net/dotnetframework/t9525-http-protocol-violation-long.aspx
*/
-
- // The server may use authentication
- if (user != null && pass != null)
- {
- NetworkCredential networkCredential;
- if (domain != null)
- {
- networkCredential = new NetworkCredential(user, pass, domain);
- }
- else
- {
- networkCredential = new NetworkCredential(user, pass);
- }
- httpWebRequest.Credentials = networkCredential;
- // Send authentication along with first request.
- httpWebRequest.PreAuthenticate = true;
- }
+ System.Net.ServicePointManager.Expect100Continue = false;
+
httpWebRequest.Method = requestMethod;
// Need to send along headers?
@@ -435,7 +455,8 @@ Task WebDavOperation(Uri uri, string requestMethod, IDictionar
// The request either contains actual content...
httpWebRequest.ContentLength = content.Length;
asyncState.content = content;
- httpWebRequest.ContentType = "text/xml";
+ if(contentType != null)
+ httpWebRequest.ContentType = "text/xml";
}
else
{
@@ -445,17 +466,17 @@ Task WebDavOperation(Uri uri, string requestMethod, IDictionar
}
// Perform asynchronous request.
- return Task.Factory.FromAsync (asyncState.request.BeginGetRequestStream, ReadCallback, asyncState)
- .ContinueWith (t => {
- if (t.IsFaulted)
- throw t.Exception;
- return Task.Factory.FromAsync (httpWebRequest.BeginGetResponse, callback, state).Result;
- });
+ return Task.Factory.FromAsync (asyncState.request.BeginGetRequestStream, ReadCallback, asyncState)
+ .ContinueWith (t => {
+ if (t.IsFaulted)
+ throw t.Exception;
+ return Task.Factory.FromAsync (httpWebRequest.BeginGetResponse, callback, state).Result;
+ });
}
else
{
// Begin async communications
- return Task.Factory.FromAsync (httpWebRequest.BeginGetResponse, callback, state);
+ return Task.Factory.FromAsync (httpWebRequest.BeginGetResponse, callback, state);
}
}
@@ -493,6 +514,14 @@ private void ReadCallback(IAsyncResult result)
}
}
}
+
+ public void Dispose()
+ {
+ if (httpWebRequest != null)
+ {
+ httpWebRequest.Abort();
+ }
+ }
#endregion
}
}