Last active
March 23, 2018 20:55
-
-
Save justincjahn/10338844 to your computer and use it in GitHub Desktop.
A simple WebDAV client class.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Net; | |
using System.Text; | |
using System.Xml; | |
namespace JahnDigital.Net.WebDAV | |
{ | |
/// <summary> | |
/// A simple class to interact with a WebDAV endpoint. | |
/// </summary> | |
public class WebDAVClient | |
{ | |
/// <summary> | |
/// Gets the absolute URI of the request. | |
/// </summary> | |
public Uri BaseURI | |
{ | |
get; | |
protected set; | |
} | |
/// <summary> | |
/// Gets or sets the identity/username of the request. | |
/// </summary> | |
public string NetworkIdentity | |
{ | |
get; | |
set; | |
} | |
/// <summary> | |
/// Sets the credential/password of the request. | |
/// </summary> | |
public string NetworkPassword | |
{ | |
protected get; | |
set; | |
} | |
/// <summary> | |
/// Configures the WebDAV client to connect to the provided URI. | |
/// </summary> | |
/// <param name="uri">The URI of the server.</param> | |
public WebDAVClient(string uri) : this(uri, string.Empty, string.Empty) {} | |
/// <summary> | |
/// Configures the WebDAV client to connect to the provided URI using credentials. | |
/// </summary> | |
/// <param name="uri">The base URI of the server.</param> | |
/// <param name="identity">The network identity/username.</param> | |
/// <param name="credential">The network credential/password.</param> | |
public WebDAVClient(string uri, string identity, string credential) | |
{ | |
// Make sure the string we were given is a proper URI | |
if (Uri.IsWellFormedUriString(uri, UriKind.Absolute)) { | |
this.BaseURI = new Uri(uri, UriKind.Absolute); | |
} else { | |
throw new InvalidOperationException("You must provide an absolute uri to this object."); | |
} | |
this.NetworkIdentity = identity; | |
this.NetworkPassword = credential; | |
// Tell .NET not to expect an HTTP 100 code when making requests. | |
System.Net.ServicePointManager.Expect100Continue = false; | |
} | |
/// <summary> | |
/// List the contents of the root collection. | |
/// </summary> | |
/// <returns>A list of file and folder paths relative to the BaseURI.</returns> | |
public List<string> ListMembers() | |
{ | |
return ListMembers("/"); | |
} | |
/// <summary> | |
/// List the contents of the provided collection. | |
/// </summary> | |
/// <param name="path">The path to the collection relative to BaseURI.</param> | |
/// <returns>A list of file and folder paths relative to the BaseURI.</returns> | |
public List<string> ListMembers(string path) | |
{ | |
return this.ListMembers(path, 1); | |
} | |
/// <summary> | |
/// List the contents and children of the provided collection. | |
/// </summary> | |
/// <param name="path">The path to the collection relative to BaseURI.</param> | |
/// <param name="depth">The amount of recursion to perform on the collections's subcollections.</param> | |
/// <returns>A list of file and folder paths relative to the BaseURI.</returns> | |
public List<string> ListMembers(string path, int depth) | |
{ | |
// Format a request body that asks the server for properties. | |
StringBuilder oPropfind = new StringBuilder(); | |
oPropfind.Append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>"); | |
oPropfind.Append("<propfind xmlns:D=\"DAV:\">"); | |
oPropfind.Append(" <propname />"); | |
oPropfind.Append("</propfind>"); | |
// Turn the request into a byte array | |
byte[] baContent = Encoding.UTF8.GetBytes(oPropfind.ToString()); | |
// Write the request | |
HttpWebRequest oRequest = this.GetRequest(path); | |
oRequest.Method = "PROPFIND"; | |
oRequest.Headers.Add("Depth", "1"); | |
using (StreamWriter oWriter = new StreamWriter(oRequest.GetRequestStream())) { | |
oWriter.Write(baContent); | |
} | |
// Read the response | |
string sResponse; | |
HttpWebResponse oResponse = (HttpWebResponse)oRequest.GetResponse(); | |
using (StreamReader oReader = new StreamReader(oResponse.GetResponseStream())) { | |
sResponse = oReader.ReadToEnd(); | |
} | |
// Check the status code. | |
/// http://webdav.org/specs/rfc4918.html#status.code.extensions.to.http11 | |
if ((int)oResponse.StatusCode != 207 && (int)oResponse.StatusCode != 200) { | |
throw new Exception(string.Format("Unable to list files: {0}", sResponse)); | |
} | |
// Parse the response to find the filenames | |
XmlDocument oXml = new XmlDocument(); | |
oXml.LoadXml(sResponse); | |
// WebDAV has a namespace, D that has a magic XMLNS DAV: | |
XmlNamespaceManager oNamespaces = new XmlNamespaceManager(oXml.NameTable); | |
oNamespaces.AddNamespace("d", "DAV:"); | |
/** | |
* Loop through each 'response' node of 'multistatus', looking for an href node. | |
* http://webdav.org/specs/rfc4918.html#METHOD_PROPFIND | |
* | |
* <D:multistatus xmlns:D="DAV:"> | |
* <D:response xmlns:R="http://ns.example.com/boxschema/"> | |
* <D:href>http://www.example.com/file</D:href> | |
* <D:propstat> | |
* <D:prop> | |
* <R:bigbox> | |
* <R:BoxType>Box type A</R:BoxType> | |
* </R:bigbox> | |
* <R:author> | |
* <R:Name>J.J. Johnson</R:Name> | |
* </R:author> | |
* <D:isFolder>t</D:isFolder> | |
* </D:prop> | |
* <D:status>HTTP/1.1 200 OK</D:status> | |
* </D:propstat> | |
* <D:propstat> | |
* <D:prop><R:DingALing/><R:Random/></D:prop> | |
* <D:status>HTTP/1.1 403 Forbidden</D:status> | |
* <D:responsedescription> The user does not have access to the property.</D:responsedescription> | |
* </D:propstat> | |
* </D:response> | |
* <D:responsedescription>There has been an access violation error.</D:responsedescription> | |
* </D:multistatus> | |
*/ | |
List<string> oReturn = new List<string>(); | |
// Determine the base path of the list | |
int iBase = 0; | |
foreach (XmlNode oNode in oXml.DocumentElement.ChildNodes) { | |
XmlNode oHREF = oNode.SelectSingleNode("d:href", oNamespaces); | |
string sFile = Uri.UnescapeDataString(oHREF.InnerText); | |
// If we don't have a base index, find it | |
if (iBase == 0) { | |
iBase = sFile.IndexOf(this.BaseURI.AbsolutePath); | |
if (iBase > 0) { | |
iBase += this.BaseURI.AbsolutePath.Length; | |
} | |
} | |
// Take off the base path if we figured it out and remove the root | |
sFile = sFile.Substring(iBase).TrimStart('/'); | |
if (sFile.Length > 0) { | |
oReturn.Add(sFile); | |
} | |
} | |
return oReturn; | |
} | |
/// <summary> | |
/// Pulls a file from the server and write it to the local filesystem. | |
/// </summary> | |
/// <param name="remotePath">Path relative to the BaseURI.</param> | |
/// <param name="localPath">Absolute path to the file.</param> | |
/// <exception cref="System.IO.IOException">Occurs when the file could not written or the disk is full.</exception> | |
/// <exception cref="System.Net.WebException">Occurs when the request to the server failed.</exception> | |
public void DownloadFile(string remotePath, string localPath) | |
{ | |
using (Stream oReader = this.GetReadStream(remotePath)) | |
using (FileStream oFileSystem = new FileStream(localPath, FileMode.Create, FileAccess.Write)) { | |
byte[] bContent = new byte[4096]; | |
int iRead = 0; | |
while ((iRead = oReader.Read(bContent, 0, bContent.Length)) > 0) { | |
oFileSystem.Write(bContent, 0, bContent.Length); | |
} | |
} | |
} | |
/// <summary> | |
/// Fetch a readable stream from the server. | |
/// </summary> | |
/// <param name="remotePath">Path relative to the BaseURI.</param> | |
/// <returns>A readable stream for which to pull file information from.</returns> | |
/// <exception cref="System.Net.WebException">Occurs when the request to the server failed.</exception> | |
protected Stream GetReadStream(string remotePath) | |
{ | |
// Form the request | |
HttpWebRequest oRequest = this.GetRequest(remotePath); | |
oRequest.Method = WebRequestMethods.Http.Get; | |
// Get the response | |
HttpWebResponse oResponse = (HttpWebResponse)oRequest.GetResponse(); | |
// Return the response | |
return oResponse.GetResponseStream(); | |
} | |
/// <summary> | |
/// Puts a file from the local filesystem to the server. | |
/// </summary> | |
/// <param name="localPath">Absolute path to the file.</param> | |
/// <param name="remotePath">Path relative to the BaseURI.</param> | |
/// <exception cref="System.UnauthorizedAccessException">If the file's properties could not be read.</exception> | |
/// <exception cref="System.Security.SecurityException">If a security-related issue with the local file occurs.</exception> | |
/// <exception cref="System.IO.FileNotFoundException">If the local file was not found.</exception> | |
public void UploadFile(string localPath, string remotePath) | |
{ | |
// Sanity check | |
if (!File.Exists(localPath)) { | |
throw new System.IO.FileNotFoundException(string.Format("The file, {0} was not found.", localPath)); | |
} | |
// Write the file to the request | |
this.UploadFile(new FileStream(localPath, FileMode.Open, FileAccess.Read), remotePath); | |
} | |
/// <summary> | |
/// Puts a stream of content on the server as a file. | |
/// </summary> | |
/// <param name="localPath">Absolute path to the file.</param> | |
/// <param name="remotePath">Path relative to the BaseURI.</param> | |
/// <exception cref="System.UnauthorizedAccessException">If the file's properties could not be read.</exception> | |
/// <exception cref="System.Security.SecurityException">If a security-related issue with the local file occurs.</exception> | |
/// <exception cref="System.IO.FileNotFoundException">If the local file was not found.</exception> | |
/// <exception cref="System.Net.WebException">If an error occurred during the request, such as HTTP 401 Unauthorized and HTTP 500 Error.</exception> | |
public void UploadFile(Stream data, string remotePath) | |
{ | |
HttpWebRequest oRequest = this.GetRequest(remotePath); | |
oRequest.Method = WebRequestMethods.Http.Put; | |
if (data.CanSeek) { | |
oRequest.ContentLength = data.Length; | |
} else { | |
throw new Exception("The stream provided must be seekable."); | |
} | |
// Write information to the body of the request. | |
byte[] bBuffer = new byte[4096]; | |
if (data.CanRead) { | |
data.Seek(0, SeekOrigin.Begin); | |
using (BinaryWriter oWrite = new BinaryWriter(oRequest.GetRequestStream())) { | |
int iRead = 0; | |
while ((iRead = data.Read(bBuffer, 0, bBuffer.Length)) > 0) { | |
oWrite.Write(bBuffer, 0, iRead); | |
} | |
} | |
} else { | |
throw new Exception("The stream provided is not readable."); | |
} | |
// Get the response | |
string sResponse; | |
HttpWebResponse oResponse = (HttpWebResponse)oRequest.GetResponse(); | |
using (StreamReader oReader = new StreamReader(oResponse.GetResponseStream())) { | |
sResponse = oReader.ReadToEnd(); | |
} | |
// Sanity check | |
if ((int)oResponse.StatusCode < 200 || (int)oResponse.StatusCode >= 300) { | |
throw new Exception(sResponse); | |
} | |
} | |
/// <summary> | |
/// Create an HttpWebRequest suitable for WebDAV. | |
/// | |
/// Note that the request method is defaulted to GET and should be changed for webdav-specific | |
/// requests, like PROPFIND. | |
/// </summary> | |
/// <param name="path">The path relative to the class URI.</param> | |
/// <returns>An HTTPWebRequest preconfigured for WebDAV requests.</returns> | |
protected HttpWebRequest GetRequest(string path = "/") | |
{ | |
// Format the path based on the URI and given path | |
string sPath = this.BaseURI.AbsoluteUri.TrimEnd('/') + "/" + path.TrimStart('/'); | |
HttpWebRequest oRequest = HttpWebRequest.CreateHttp(sPath); | |
// WebDAV is XML-based | |
oRequest.ContentType = "text/xml"; | |
// If we have credentials, add them | |
if (this.NetworkPassword != string.Empty && this.NetworkIdentity != string.Empty) { | |
NetworkCredential oCredential = new NetworkCredential(this.NetworkIdentity, this.NetworkPassword); | |
oRequest.Credentials = oCredential; | |
oRequest.PreAuthenticate = true; | |
} | |
return oRequest; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment