Skip to content

Instantly share code, notes, and snippets.

@justincjahn
Last active March 23, 2018 20:55
Show Gist options
  • Save justincjahn/10338844 to your computer and use it in GitHub Desktop.
Save justincjahn/10338844 to your computer and use it in GitHub Desktop.
A simple WebDAV client class.
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