411 lines
15 KiB
C#
411 lines
15 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.IO;
|
||
|
using System.Text;
|
||
|
using System.Web;
|
||
|
using System.Xml;
|
||
|
using Common.Logging;
|
||
|
using WebDAVSharp.Server.Adapters;
|
||
|
using WebDAVSharp.Server.Exceptions;
|
||
|
using WebDAVSharp.Server.Stores;
|
||
|
using WebDAVSharp.Server.Utilities;
|
||
|
|
||
|
namespace WebDAVSharp.Server.MethodHandlers {
|
||
|
/// <summary>
|
||
|
/// This class implements the <c>PROPFIND</c> HTTP method for WebDAV#.
|
||
|
/// </summary>
|
||
|
internal class WebDavPropfindMethodHandler : WebDavMethodHandlerBase, IWebDavMethodHandler {
|
||
|
private ILog _log;
|
||
|
private String _requestedFilePath;
|
||
|
private List<WebDavProperty> _requestedProperties;
|
||
|
private List<IWebDavStoreItem> _webDavStoreItems;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the collection of the names of the HTTP methods handled by this instance.
|
||
|
/// </summary>
|
||
|
/// <value>
|
||
|
/// The names.
|
||
|
/// </value>
|
||
|
public IEnumerable<string> Names {
|
||
|
get {
|
||
|
return new[]
|
||
|
{
|
||
|
"PROPFIND"
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Processes the request.
|
||
|
/// </summary>
|
||
|
/// <param name="server">The <see cref="WebDavServer" /> through which the request came in from the client.</param>
|
||
|
/// <param name="context">The
|
||
|
/// <see cref="IHttpListenerContext" /> object containing both the request and response
|
||
|
/// objects to use.</param>
|
||
|
/// <param name="store">The <see cref="IWebDavStore" /> that the <see cref="WebDavServer" /> is hosting.</param>
|
||
|
/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavUnauthorizedException"></exception>
|
||
|
public void ProcessRequest(WebDavServer server, HttpContext context, IWebDavStore store, String fileName) {
|
||
|
_log = LogManager.GetCurrentClassLogger();
|
||
|
|
||
|
/***************************************************************************************************
|
||
|
* Retreive all the information from the request
|
||
|
***************************************************************************************************/
|
||
|
|
||
|
// Read the headers, ...
|
||
|
bool isPropname = false;
|
||
|
int depth = GetDepthHeader(context.Request);
|
||
|
_requestedFilePath = server.ServiceUrl + fileName;//GetRequestUri(context.Request.Url2.ToString());
|
||
|
// make sure _requestedFilePath ends with /
|
||
|
_requestedFilePath = _requestedFilePath.TrimEnd('/') + '/';
|
||
|
|
||
|
try {
|
||
|
_webDavStoreItems = GetWebDavStoreItems(GetItem(server, store, fileName), depth);
|
||
|
}
|
||
|
catch (UnauthorizedAccessException) {
|
||
|
throw new WebDavUnauthorizedException();
|
||
|
}
|
||
|
|
||
|
// Get the XmlDocument from the request
|
||
|
XmlDocument requestDoc = GetXmlDocument(context.Request);
|
||
|
|
||
|
// See what is requested
|
||
|
_requestedProperties = new List<WebDavProperty>();
|
||
|
if (requestDoc.DocumentElement != null) {
|
||
|
if (requestDoc.DocumentElement.LocalName != "propfind")
|
||
|
_log.Debug("PROPFIND method without propfind in xml document");
|
||
|
else {
|
||
|
XmlNode n = requestDoc.DocumentElement.FirstChild;
|
||
|
if (n == null)
|
||
|
_log.Debug("propfind element without children");
|
||
|
else {
|
||
|
switch (n.LocalName) {
|
||
|
case "allprop":
|
||
|
_requestedProperties = GetAllProperties();
|
||
|
break;
|
||
|
case "propname":
|
||
|
isPropname = true;
|
||
|
_requestedProperties = GetAllProperties();
|
||
|
break;
|
||
|
case "prop":
|
||
|
foreach (XmlNode child in n.ChildNodes)
|
||
|
_requestedProperties.Add(new WebDavProperty(child.LocalName, "", child.NamespaceURI));
|
||
|
break;
|
||
|
default:
|
||
|
_requestedProperties.Add(new WebDavProperty(n.LocalName, "", n.NamespaceURI));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
_requestedProperties = GetAllProperties();
|
||
|
|
||
|
/***************************************************************************************************
|
||
|
* Create the body for the response
|
||
|
***************************************************************************************************/
|
||
|
|
||
|
XmlDocument responseDoc = ResponseDocument(context, isPropname);
|
||
|
|
||
|
/***************************************************************************************************
|
||
|
* Send the response
|
||
|
***************************************************************************************************/
|
||
|
|
||
|
SendResponse(context, responseDoc);
|
||
|
}
|
||
|
|
||
|
#region RetrieveInformation
|
||
|
|
||
|
/// <summary>
|
||
|
/// Get the URI to the location
|
||
|
/// If no slash at the end of the URI, this method adds one
|
||
|
/// </summary>
|
||
|
/// <param name="uri">The <see cref="string" /> that contains the URI</param>
|
||
|
/// <returns>
|
||
|
/// The <see cref="Uri" /> that contains the given uri
|
||
|
/// </returns>
|
||
|
private static Uri GetRequestUri(string uri) {
|
||
|
return new Uri(uri.EndsWith("/") ? uri : uri + "/");
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Convert the given
|
||
|
/// <see cref="IWebDavStoreItem" /> to a
|
||
|
/// <see cref="List{T}" /> of
|
||
|
/// <see cref="IWebDavStoreItem" />
|
||
|
/// This list depends on the "Depth" header
|
||
|
/// </summary>
|
||
|
/// <param name="iWebDavStoreItem">The <see cref="IWebDavStoreItem" /> that needs to be converted</param>
|
||
|
/// <param name="depth">The "Depth" header</param>
|
||
|
/// <returns>
|
||
|
/// A <see cref="List{T}" /> of <see cref="IWebDavStoreItem" />
|
||
|
/// </returns>
|
||
|
/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavConflictException"></exception>
|
||
|
private static List<IWebDavStoreItem> GetWebDavStoreItems(IWebDavStoreItem iWebDavStoreItem, int depth) {
|
||
|
ILog _log = LogManager.GetCurrentClassLogger();
|
||
|
List<IWebDavStoreItem> list = new List<IWebDavStoreItem>();
|
||
|
|
||
|
//IWebDavStoreCollection
|
||
|
// if the item is a collection
|
||
|
IWebDavStoreCollection collection = iWebDavStoreItem as IWebDavStoreCollection;
|
||
|
if (collection != null) {
|
||
|
list.Add(collection);
|
||
|
if (depth == 0)
|
||
|
return list;
|
||
|
foreach (IWebDavStoreItem item in collection.Items) {
|
||
|
try {
|
||
|
list.Add(item);
|
||
|
}
|
||
|
catch (Exception ex) {
|
||
|
_log.Debug(ex.Message + "\r\n" + ex.StackTrace);
|
||
|
}
|
||
|
}
|
||
|
return list;
|
||
|
}
|
||
|
// if the item is not a document, throw conflict exception
|
||
|
if (!(iWebDavStoreItem is IWebDavStoreDocument))
|
||
|
throw new WebDavConflictException();
|
||
|
|
||
|
// add the item to the list
|
||
|
list.Add(iWebDavStoreItem);
|
||
|
|
||
|
return list;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reads the XML body of the
|
||
|
/// <see cref="IHttpListenerRequest" />
|
||
|
/// and converts it to an
|
||
|
/// <see cref="XmlDocument" />
|
||
|
/// </summary>
|
||
|
/// <param name="request">The <see cref="IHttpListenerRequest" /></param>
|
||
|
/// <returns>
|
||
|
/// The <see cref="XmlDocument" /> that contains the request body
|
||
|
/// </returns>
|
||
|
private XmlDocument GetXmlDocument(HttpRequest request) {
|
||
|
try {
|
||
|
StreamReader reader = new StreamReader(request.InputStream, Encoding.UTF8);
|
||
|
string requestBody = reader.ReadToEnd();
|
||
|
reader.Close();
|
||
|
|
||
|
if (!String.IsNullOrEmpty(requestBody)) {
|
||
|
var xmlDocument = new XmlDocument();
|
||
|
xmlDocument.LoadXml(requestBody);
|
||
|
return xmlDocument;
|
||
|
}
|
||
|
}
|
||
|
catch (Exception) {
|
||
|
_log.Warn("XmlDocument has not been read correctly");
|
||
|
}
|
||
|
|
||
|
return new XmlDocument();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Adds the standard properties for an Propfind allprop request to a <see cref="List{T}" /> of <see cref="WebDavProperty" />
|
||
|
/// </summary>
|
||
|
/// <returns>
|
||
|
/// The list with all the <see cref="WebDavProperty" />
|
||
|
/// </returns>
|
||
|
private List<WebDavProperty> GetAllProperties() {
|
||
|
List<WebDavProperty> list = new List<WebDavProperty>
|
||
|
{
|
||
|
new WebDavProperty("creationdate"),
|
||
|
new WebDavProperty("displayname"),
|
||
|
new WebDavProperty("getcontentlength"),
|
||
|
new WebDavProperty("getcontenttype"),
|
||
|
new WebDavProperty("getetag"),
|
||
|
new WebDavProperty("getlastmodified"),
|
||
|
new WebDavProperty("resourcetype"),
|
||
|
new WebDavProperty("supportedlock"),
|
||
|
new WebDavProperty("ishidden")
|
||
|
};
|
||
|
//list.Add(new WebDAVProperty("getcontentlanguage"));
|
||
|
//list.Add(new WebDAVProperty("lockdiscovery"));
|
||
|
return list;
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region BuildResponseBody
|
||
|
|
||
|
/// <summary>
|
||
|
/// Builds the <see cref="XmlDocument" /> containing the response body
|
||
|
/// </summary>
|
||
|
/// <param name="context">The <see cref="IHttpListenerContext" /></param>
|
||
|
/// <param name="propname">The boolean defining the Propfind propname request</param>
|
||
|
/// <returns>
|
||
|
/// The <see cref="XmlDocument" /> containing the response body
|
||
|
/// </returns>
|
||
|
private XmlDocument ResponseDocument(HttpContext context, bool propname) {
|
||
|
// Create the basic response XmlDocument
|
||
|
XmlDocument responseDoc = new XmlDocument();
|
||
|
const string responseXml = "<?xml version=\"1.0\"?><D:multistatus xmlns:D=\"DAV:\"></D:multistatus>";
|
||
|
responseDoc.LoadXml(responseXml);
|
||
|
|
||
|
// Generate the manager
|
||
|
XmlNamespaceManager manager = new XmlNamespaceManager(responseDoc.NameTable);
|
||
|
manager.AddNamespace("D", "DAV:");
|
||
|
manager.AddNamespace("Office", "schemas-microsoft-com:office:office");
|
||
|
manager.AddNamespace("Repl", "http://schemas.microsoft.com/repl/");
|
||
|
manager.AddNamespace("Z", "urn:schemas-microsoft-com:");
|
||
|
|
||
|
int count = 0;
|
||
|
|
||
|
foreach (IWebDavStoreItem webDavStoreItem in _webDavStoreItems) {
|
||
|
// Create the response element
|
||
|
WebDavProperty responseProperty = new WebDavProperty("response", "");
|
||
|
XmlElement responseElement = responseProperty.ToXmlElement(responseDoc);
|
||
|
|
||
|
// The href element
|
||
|
Uri result;
|
||
|
//String result;
|
||
|
if (count == 0) {
|
||
|
Uri.TryCreate(new Uri(_requestedFilePath), "", out result);
|
||
|
//result = _requestedFilePath;
|
||
|
}
|
||
|
else {
|
||
|
Uri.TryCreate(new Uri(_requestedFilePath), webDavStoreItem.Name, out result);
|
||
|
//result = _requestedFilePath + webDavStoreItem.Name;
|
||
|
}
|
||
|
WebDavProperty hrefProperty = new WebDavProperty("href", result.AbsoluteUri);
|
||
|
responseElement.AppendChild(hrefProperty.ToXmlElement(responseDoc));
|
||
|
count++;
|
||
|
|
||
|
// The propstat element
|
||
|
WebDavProperty propstatProperty = new WebDavProperty("propstat", "");
|
||
|
XmlElement propstatElement = propstatProperty.ToXmlElement(responseDoc);
|
||
|
|
||
|
// The prop element
|
||
|
WebDavProperty propProperty = new WebDavProperty("prop", "");
|
||
|
XmlElement propElement = propProperty.ToXmlElement(responseDoc);
|
||
|
|
||
|
foreach (WebDavProperty davProperty in _requestedProperties) {
|
||
|
propElement.AppendChild(PropChildElement(davProperty, responseDoc, webDavStoreItem, propname));
|
||
|
}
|
||
|
|
||
|
// Add the prop element to the propstat element
|
||
|
propstatElement.AppendChild(propElement);
|
||
|
|
||
|
// The status element
|
||
|
WebDavProperty statusProperty = new WebDavProperty("status",
|
||
|
"HTTP/1.1 " + context.Response.StatusCode + " " +
|
||
|
HttpWorkerRequest.GetStatusDescription(context.Response.StatusCode));
|
||
|
propstatElement.AppendChild(statusProperty.ToXmlElement(responseDoc));
|
||
|
|
||
|
// Add the propstat element to the response element
|
||
|
responseElement.AppendChild(propstatElement);
|
||
|
|
||
|
// Add the response element to the multistatus element
|
||
|
responseDoc.DocumentElement.AppendChild(responseElement);
|
||
|
}
|
||
|
|
||
|
return responseDoc;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gives the
|
||
|
/// <see cref="XmlElement" /> of a
|
||
|
/// <see cref="WebDavProperty" />
|
||
|
/// with or without values
|
||
|
/// or with or without child elements
|
||
|
/// </summary>
|
||
|
/// <param name="webDavProperty">The <see cref="WebDavProperty" /></param>
|
||
|
/// <param name="xmlDocument">The <see cref="XmlDocument" /> containing the response body</param>
|
||
|
/// <param name="iWebDavStoreItem">The <see cref="IWebDavStoreItem" /></param>
|
||
|
/// <param name="isPropname">The boolean defining the Propfind propname request</param>
|
||
|
/// <returns>
|
||
|
/// The <see cref="XmlElement" /> of the <see cref="WebDavProperty" /> containing a value or child elements
|
||
|
/// </returns>
|
||
|
private XmlElement PropChildElement(WebDavProperty webDavProperty, XmlDocument xmlDocument, IWebDavStoreItem iWebDavStoreItem, bool isPropname) {
|
||
|
// If Propfind request contains a propname element
|
||
|
if (isPropname) {
|
||
|
webDavProperty.Value = String.Empty;
|
||
|
return webDavProperty.ToXmlElement(xmlDocument);
|
||
|
}
|
||
|
// If not, add the values to webDavProperty
|
||
|
webDavProperty.Value = GetWebDavPropertyValue(iWebDavStoreItem, webDavProperty);
|
||
|
XmlElement xmlElement = webDavProperty.ToXmlElement(xmlDocument);
|
||
|
|
||
|
// If the webDavProperty is the resourcetype property
|
||
|
// and the webDavStoreItem is a collection
|
||
|
// add the collection XmlElement as a child to the xmlElement
|
||
|
if (webDavProperty.Name != "resourcetype" || !iWebDavStoreItem.IsCollection)
|
||
|
return xmlElement;
|
||
|
|
||
|
WebDavProperty collectionProperty = new WebDavProperty("collection", "");
|
||
|
xmlElement.AppendChild(collectionProperty.ToXmlElement(xmlDocument));
|
||
|
return xmlElement;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the correct value for a <see cref="WebDavProperty" />
|
||
|
/// </summary>
|
||
|
/// <param name="webDavStoreItem">The <see cref="IWebDavStoreItem" /> defines the values</param>
|
||
|
/// <param name="davProperty">The <see cref="WebDavProperty" /> that needs a value</param>
|
||
|
/// <returns>
|
||
|
/// A <see cref="string" /> containing the value
|
||
|
/// </returns>
|
||
|
private string GetWebDavPropertyValue(IWebDavStoreItem webDavStoreItem, WebDavProperty davProperty) {
|
||
|
switch (davProperty.Name) {
|
||
|
case "creationdate":
|
||
|
return webDavStoreItem.CreationDate.ToUniversalTime().ToString("s") + "Z";
|
||
|
case "displayname":
|
||
|
return webDavStoreItem.Name;
|
||
|
case "getcontentlanguage":
|
||
|
// still to implement !!!
|
||
|
return String.Empty;
|
||
|
case "getcontentlength":
|
||
|
return (!webDavStoreItem.IsCollection ? "" + ((IWebDavStoreDocument)webDavStoreItem).Size : "");
|
||
|
case "getcontenttype":
|
||
|
return (!webDavStoreItem.IsCollection ? "" + ((IWebDavStoreDocument)webDavStoreItem).MimeType : "");
|
||
|
case "getetag":
|
||
|
return (!webDavStoreItem.IsCollection ? "" + ((IWebDavStoreDocument)webDavStoreItem).Etag : "");
|
||
|
case "getlastmodified":
|
||
|
return webDavStoreItem.ModificationDate.ToUniversalTime().ToString("R");
|
||
|
case "lockdiscovery":
|
||
|
// still to implement !!!
|
||
|
return String.Empty;
|
||
|
case "resourcetype":
|
||
|
return "";
|
||
|
case "supportedlock":
|
||
|
// still to implement !!!
|
||
|
return "";
|
||
|
//webDavProperty.Value = "<D:lockentry><D:lockscope><D:shared/></D:lockscope><D:locktype><D:write/></D:locktype></D:lockentry>";
|
||
|
case "ishidden":
|
||
|
return "" + webDavStoreItem.Hidden;
|
||
|
default:
|
||
|
return String.Empty;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region SendResponse
|
||
|
|
||
|
/// <summary>
|
||
|
/// Sends the response
|
||
|
/// </summary>
|
||
|
/// <param name="context">The <see cref="IHttpListenerContext" /> containing the response</param>
|
||
|
/// <param name="responseDocument">The <see cref="XmlDocument" /> containing the response body</param>
|
||
|
private static void SendResponse(HttpContext context, XmlDocument responseDocument) {
|
||
|
// convert the XmlDocument
|
||
|
byte[] responseBytes = Encoding.UTF8.GetBytes(responseDocument.InnerXml);
|
||
|
|
||
|
// HttpStatusCode doesn't contain WebDav status codes, but HttpWorkerRequest can handle these WebDav status codes
|
||
|
context.Response.StatusCode = (int)WebDavStatusCode.MultiStatus;
|
||
|
context.Response.StatusDescription = HttpWorkerRequest.GetStatusDescription((int)WebDavStatusCode.MultiStatus);
|
||
|
|
||
|
//context.Response.ContentLength64 = responseBytes.Length;
|
||
|
//context.Response.AdaptedInstance.ContentType = "text/xml";
|
||
|
context.Response.ContentType = "text/xml";
|
||
|
|
||
|
context.Response.OutputStream.Write(responseBytes, 0, responseBytes.Length);
|
||
|
|
||
|
//context.Response.Close();
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
}
|
||
|
}
|