diff --git a/Adapters/AuthenticationTypes/HttpListenerAnyonymousAdapter.cs b/Adapters/AuthenticationTypes/HttpListenerAnyonymousAdapter.cs new file mode 100644 index 0000000..f9139a6 --- /dev/null +++ b/Adapters/AuthenticationTypes/HttpListenerAnyonymousAdapter.cs @@ -0,0 +1,74 @@ +using System; +using System.Net; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Principal; +using System.Threading; +using Microsoft.Win32.SafeHandles; + +namespace WebDAVSharp.Server.Adapters.AuthenticationTypes +{ + class HttpListenerAnyonymousAdapter : WebDavDisposableBase, IHttpListener, IAdapter + { + public HttpListenerAnyonymousAdapter() + { + AdaptedInstance = new HttpListener + { + AuthenticationSchemes = AuthenticationSchemes.Anonymous, + UnsafeConnectionNtlmAuthentication = false + }; + } + + protected override void Dispose(bool disposing) + { + if (AdaptedInstance.IsListening) + AdaptedInstance.Close(); + } + + public HttpListener AdaptedInstance + { + get; + private set; + } + + public IHttpListenerContext GetContext(EventWaitHandle abortEvent) + { + if (abortEvent == null) + throw new ArgumentNullException("abortEvent"); + + IAsyncResult ar = AdaptedInstance.BeginGetContext(null, null); + int index = WaitHandle.WaitAny(new[] { abortEvent, ar.AsyncWaitHandle }); + if (index != 1) + return null; + HttpListenerContext context = AdaptedInstance.EndGetContext(ar); + return new HttpListenerContextAdapter(context); + } + + public HttpListenerPrefixCollection Prefixes + { + get + { + return AdaptedInstance.Prefixes; + } + } + + public void Start() + { + AdaptedInstance.Start(); + } + + public void Stop() + { + AdaptedInstance.Stop(); + } + + public IIdentity GetIdentity(IHttpListenerContext context) + { + return WindowsIdentity.GetCurrent(); + } + + + + } +} diff --git a/Adapters/AuthenticationTypes/HttpListenerBasicAdapter.cs b/Adapters/AuthenticationTypes/HttpListenerBasicAdapter.cs new file mode 100644 index 0000000..d47e276 --- /dev/null +++ b/Adapters/AuthenticationTypes/HttpListenerBasicAdapter.cs @@ -0,0 +1,124 @@ +using System; +using System.Net; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Principal; +using System.Threading; +using Microsoft.Win32.SafeHandles; + +namespace WebDAVSharp.Server.Adapters.AuthenticationTypes +{ + class HttpListenerBasicAdapter : WebDavDisposableBase, IHttpListener, IAdapter + { + #region Imports + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto)] + public static extern bool CloseHandle(IntPtr handle); + + #endregion + + public HttpListenerBasicAdapter() + { + AdaptedInstance = new HttpListener + { + AuthenticationSchemes = AuthenticationSchemes.Basic, + UnsafeConnectionNtlmAuthentication = false + }; + } + + protected override void Dispose(bool disposing) + { + if (AdaptedInstance.IsListening) + AdaptedInstance.Close(); + } + + public HttpListener AdaptedInstance + { + get; + private set; + } + + public IHttpListenerContext GetContext(EventWaitHandle abortEvent) + { + if (abortEvent == null) + throw new ArgumentNullException("abortEvent"); + + IAsyncResult ar = AdaptedInstance.BeginGetContext(null, null); + int index = WaitHandle.WaitAny(new[] { abortEvent, ar.AsyncWaitHandle }); + if (index != 1) + return null; + HttpListenerContext context = AdaptedInstance.EndGetContext(ar); + return new HttpListenerContextAdapter(context); + } + + public HttpListenerPrefixCollection Prefixes + { + get + { + return AdaptedInstance.Prefixes; + } + } + + public void Start() + { + AdaptedInstance.Start(); + } + + public void Stop() + { + AdaptedInstance.Stop(); + } + + public IIdentity GetIdentity(IHttpListenerContext context) + { + HttpListenerBasicIdentity ident = (HttpListenerBasicIdentity)context.AdaptedInstance.User.Identity; + string domain = ident.Name.Split('\\')[0]; + string username = ident.Name.Split('\\')[1]; + var token = GetToken(domain, username, ident.Password); + return new WindowsIdentity(token.DangerousGetHandle()); + } + + + + internal static SafeTokenHandle GetToken(string domainName, + string userName, string password) + { + SafeTokenHandle safeTokenHandle; + + const int LOGON32_PROVIDER_DEFAULT = 0; + //This parameter causes LogonUser to create a primary token. + const int LOGON32_LOGON_INTERACTIVE = 2; + + // Call LogonUser to obtain a handle to an access token. + bool returnValue = LogonUser(userName, domainName, password, + LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, + out safeTokenHandle); + + if (returnValue) return safeTokenHandle; + int ret = Marshal.GetLastWin32Error(); + throw new System.ComponentModel.Win32Exception(ret); + } + + public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid + { + private SafeTokenHandle() + : base(true) + { + } + + [DllImport("kernel32.dll")] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [SuppressUnmanagedCodeSecurity] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool CloseHandle(IntPtr handle); + + protected override bool ReleaseHandle() + { + return CloseHandle(handle); + } + } + } +} diff --git a/Adapters/HttpListenerAdapter.cs b/Adapters/AuthenticationTypes/HttpListenerNegotiateAdapter.cs similarity index 83% rename from Adapters/HttpListenerAdapter.cs rename to Adapters/AuthenticationTypes/HttpListenerNegotiateAdapter.cs index c5c8f42..e1dad55 100644 --- a/Adapters/HttpListenerAdapter.cs +++ b/Adapters/AuthenticationTypes/HttpListenerNegotiateAdapter.cs @@ -1,22 +1,58 @@ using System; using System.Net; +using System.Security.Principal; using System.Threading; -namespace WebDAVSharp.Server.Adapters +namespace WebDAVSharp.Server.Adapters.AuthenticationTypes { /// /// This /// implementation wraps around a /// instance. /// - internal sealed class HttpListenerAdapter : WebDavDisposableBase, IHttpListener, IAdapter + internal sealed class HttpListenerNegotiateAdapter : WebDavDisposableBase, IHttpListener, IAdapter { + #region Private Variables + private readonly HttpListener _listener; + #endregion + + #region Properties /// - /// Initializes a new instance of the class. + /// Gets the internal instance that was adapted for WebDAV#. /// - internal HttpListenerAdapter() + /// + /// The adapted instance. + /// + public HttpListener AdaptedInstance + { + get + { + return _listener; + } + } + + /// + /// Gets the Uniform Resource Identifier ( + /// ) prefixes handled by the + /// adapted + /// object. + /// + public HttpListenerPrefixCollection Prefixes + { + get + { + return _listener.Prefixes; + } + } + #endregion + + #region Constructor + /// + /// Initializes a new instance of the class. + /// + internal HttpListenerNegotiateAdapter() { _listener = new HttpListener { @@ -24,7 +60,9 @@ internal HttpListenerAdapter() UnsafeConnectionNtlmAuthentication = false }; } + #endregion + #region Function Overrides /// /// Releases unmanaged and - optionally - managed resources /// @@ -35,6 +73,10 @@ protected override void Dispose(bool disposing) _listener.Close(); } + #endregion + + #region Public Functions + /// /// Waits for a request to come in to the web server and returns a /// adapter around it. @@ -63,35 +105,7 @@ public IHttpListenerContext GetContext(EventWaitHandle abortEvent) HttpListenerContext context = _listener.EndGetContext(ar); return new HttpListenerContextAdapter(context); } - - /// - /// Gets the internal instance that was adapted for WebDAV#. - /// - /// - /// The adapted instance. - /// - public HttpListener AdaptedInstance - { - get - { - return _listener; - } - } - - /// - /// Gets the Uniform Resource Identifier ( - /// ) prefixes handled by the - /// adapted - /// object. - /// - public HttpListenerPrefixCollection Prefixes - { - get - { - return _listener.Prefixes; - } - } - + /// /// Allows the adapted to receive incoming requests. /// @@ -107,5 +121,12 @@ public void Stop() { _listener.Stop(); } + + public IIdentity GetIdentity(IHttpListenerContext context) + { + return context.AdaptedInstance.User.Identity; + } + + #endregion } } \ No newline at end of file diff --git a/Adapters/HttpListenerContextAdapter.cs b/Adapters/HttpListenerContextAdapter.cs index 4eecbac..3729eec 100644 --- a/Adapters/HttpListenerContextAdapter.cs +++ b/Adapters/HttpListenerContextAdapter.cs @@ -8,12 +8,18 @@ namespace WebDAVSharp.Server.Adapters /// implementation wraps around a /// instance. /// - internal sealed class HttpListenerContextAdapter : IHttpListenerContext, IAdapter + public sealed class HttpListenerContextAdapter : IHttpListenerContext, IAdapter { + #region Private Variables + private readonly HttpListenerContext _context; private readonly HttpListenerRequestAdapter _request; private readonly HttpListenerResponseAdapter _response; + #endregion + + #region Public Functions + /// /// Initializes a new instance of the class. /// @@ -30,6 +36,10 @@ public HttpListenerContextAdapter(HttpListenerContext context) _response = new HttpListenerResponseAdapter(context.Response); } + #endregion + + #region Properties + /// /// Gets the internal instance that was adapted for WebDAV#. /// @@ -65,5 +75,7 @@ public IHttpListenerResponse Response return _response; } } + + #endregion } } \ No newline at end of file diff --git a/Adapters/HttpListenerRequestAdapter.cs b/Adapters/HttpListenerRequestAdapter.cs index e259bf1..00063ad 100644 --- a/Adapters/HttpListenerRequestAdapter.cs +++ b/Adapters/HttpListenerRequestAdapter.cs @@ -13,8 +13,13 @@ namespace WebDAVSharp.Server.Adapters /// internal sealed class HttpListenerRequestAdapter : IHttpListenerRequest { + #region Private Variables + private readonly HttpListenerRequest _request; + #endregion + + #region Public Functions /// /// Initializes a new instance of the class. /// @@ -29,6 +34,10 @@ public HttpListenerRequestAdapter(HttpListenerRequest request) _request = request; } + #endregion + + #region Properties + /// /// Gets the internal instance that was adapted for WebDAV#. /// @@ -119,5 +128,7 @@ public long ContentLength64 return _request.ContentLength64; } } + + #endregion } } \ No newline at end of file diff --git a/Adapters/HttpListenerResponseAdapter.cs b/Adapters/HttpListenerResponseAdapter.cs index be5e686..0ff6dd4 100644 --- a/Adapters/HttpListenerResponseAdapter.cs +++ b/Adapters/HttpListenerResponseAdapter.cs @@ -12,22 +12,13 @@ namespace WebDAVSharp.Server.Adapters /// internal sealed class HttpListenerResponseAdapter : IHttpListenerResponse { - private readonly HttpListenerResponse _response; + #region Private Variables - /// - /// Initializes a new instance of the class. - /// - /// The to adapt for WebDAV#. - /// Response - /// is null. - public HttpListenerResponseAdapter(HttpListenerResponse Response) - { - if (Response == null) - throw new ArgumentNullException("Response"); + private readonly HttpListenerResponse _response; - _response = Response; - } + #endregion + #region Properties /// /// Gets the internal instance that was adapted for WebDAV#. /// @@ -117,6 +108,24 @@ public long ContentLength64 } } + #endregion + + #region Public Functions + + /// + /// Initializes a new instance of the class. + /// + /// The to adapt for WebDAV#. + /// Response + /// is null. + public HttpListenerResponseAdapter(HttpListenerResponse response) + { + if (response == null) + throw new ArgumentNullException("response"); + + _response = response; + } + /// /// Sends the response to the client and releases the resources held by the adapted /// instance. @@ -135,5 +144,7 @@ public void AppendHeader(string name, string value) { _response.AppendHeader(name, value); } + + #endregion } } \ No newline at end of file diff --git a/Adapters/IHttpListener.cs b/Adapters/IHttpListener.cs index ac57b19..bcee94e 100644 --- a/Adapters/IHttpListener.cs +++ b/Adapters/IHttpListener.cs @@ -1,5 +1,6 @@ using System; using System.Net; +using System.Security.Principal; using System.Threading; namespace WebDAVSharp.Server.Adapters @@ -55,5 +56,12 @@ HttpListenerPrefixCollection Prefixes /// Causes the adapted to stop receiving incoming requests. /// void Stop(); + + /// + /// Returns the windows Idenity to use for the request. + /// + /// + /// + IIdentity GetIdentity(IHttpListenerContext context); } } \ No newline at end of file diff --git a/MethodHandlers/WebDAVCopyMethodHandler.cs b/MethodHandlers/WebDAVCopyMethodHandler.cs index 39504db..52a44d0 100644 --- a/MethodHandlers/WebDAVCopyMethodHandler.cs +++ b/MethodHandlers/WebDAVCopyMethodHandler.cs @@ -13,6 +13,8 @@ namespace WebDAVSharp.Server.MethodHandlers /// internal class WebDavCopyMethodHandler : WebDavMethodHandlerBase, IWebDavMethodHandler { + #region Properties + /// /// Gets the collection of the names of the HTTP methods handled by this instance. /// @@ -30,6 +32,10 @@ public IEnumerable Names } } + #endregion + + #region Public Functions + /// /// Processes the request. /// @@ -82,7 +88,10 @@ private static void CopyItem(WebDavServer server, IHttpListenerContext context, destinationParentCollection.CopyItemHere(source, destinationName, copyContent); - context.SendSimpleResponse(isNew ? HttpStatusCode.Created : HttpStatusCode.NoContent); + context.SendSimpleResponse(isNew ? (int)HttpStatusCode.Created : (int)HttpStatusCode.NoContent); } + + #endregion + } } \ No newline at end of file diff --git a/MethodHandlers/WebDAVDeleteMethodHandler.cs b/MethodHandlers/WebDAVDeleteMethodHandler.cs index 539240a..205cc26 100644 --- a/MethodHandlers/WebDAVDeleteMethodHandler.cs +++ b/MethodHandlers/WebDAVDeleteMethodHandler.cs @@ -9,6 +9,9 @@ namespace WebDAVSharp.Server.MethodHandlers /// internal class WebDavDeleteMethodHandler : WebDavMethodHandlerBase, IWebDavMethodHandler { + + #region Properties + /// /// Gets the collection of the names of the HTTP methods handled by this instance. /// @@ -26,6 +29,10 @@ public IEnumerable Names } } + #endregion + + #region Functions + /// /// Processes the request. /// @@ -46,5 +53,7 @@ public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IW collection.Delete(item); context.SendSimpleResponse(); } + + #endregion } } \ No newline at end of file diff --git a/MethodHandlers/WebDAVGetMethodHandler.cs b/MethodHandlers/WebDAVGetMethodHandler.cs index f48f18c..f35474f 100644 --- a/MethodHandlers/WebDAVGetMethodHandler.cs +++ b/MethodHandlers/WebDAVGetMethodHandler.cs @@ -12,6 +12,9 @@ namespace WebDAVSharp.Server.MethodHandlers /// internal sealed class WebDavGetMethodHandler : WebDavMethodHandlerBase, IWebDavMethodHandler { + + #region Properties + /// /// Gets the collection of the names of the verbs handled by this instance. /// @@ -29,6 +32,10 @@ public IEnumerable Names } } + #endregion + + #region Functions + /// /// Processes the request. /// @@ -61,17 +68,29 @@ public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IW using (Stream stream = doc.OpenReadStream()) { - context.Response.StatusCode = (int)HttpStatusCode.OK; + if (stream == null) + { + context.Response.StatusCode = (int)HttpStatusCode.OK; + context.Response.ContentLength64 = 0; + } + else + { + context.Response.StatusCode = (int)HttpStatusCode.OK; - if (docSize > 0) - context.Response.ContentLength64 = docSize; + if (docSize > 0) + context.Response.ContentLength64 = docSize; - byte[] buffer = new byte[4096]; - int inBuffer; - while ((inBuffer = stream.Read(buffer, 0, buffer.Length)) > 0) - context.Response.OutputStream.Write(buffer, 0, inBuffer); + byte[] buffer = new byte[4096]; + int inBuffer; + while ((inBuffer = stream.Read(buffer, 0, buffer.Length)) > 0) + context.Response.OutputStream.Write(buffer, 0, inBuffer); + context.Response.OutputStream.Flush(); + } } + context.Response.Close(); } + + #endregion } } \ No newline at end of file diff --git a/MethodHandlers/WebDAVHeadMethodHandler.cs b/MethodHandlers/WebDAVHeadMethodHandler.cs index f28d3f6..80dddf6 100644 --- a/MethodHandlers/WebDAVHeadMethodHandler.cs +++ b/MethodHandlers/WebDAVHeadMethodHandler.cs @@ -12,6 +12,8 @@ namespace WebDAVSharp.Server.MethodHandlers /// internal class WebDavHeadMethodHandler : WebDavMethodHandlerBase, IWebDavMethodHandler { + + #region Properties /// /// Gets the collection of the names of the HTTP methods handled by this instance. /// @@ -29,6 +31,10 @@ public IEnumerable Names } } + #endregion + + #region Functions + /// /// Processes the request. /// @@ -66,5 +72,8 @@ public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IW context.Response.Close(); } + + #endregion + } } \ No newline at end of file diff --git a/MethodHandlers/WebDAVLockMethodHandler.cs b/MethodHandlers/WebDAVLockMethodHandler.cs index b5dff85..c7d60a4 100644 --- a/MethodHandlers/WebDAVLockMethodHandler.cs +++ b/MethodHandlers/WebDAVLockMethodHandler.cs @@ -3,13 +3,15 @@ using System.IO; using System.Linq; using System.Net; +using System.Security.Principal; using System.Text; +using System.Threading; using System.Web; using System.Xml; -using Common.Logging; using WebDAVSharp.Server.Adapters; using WebDAVSharp.Server.Exceptions; using WebDAVSharp.Server.Stores; +using WebDAVSharp.Server.Stores.Locks; namespace WebDAVSharp.Server.MethodHandlers { @@ -18,6 +20,9 @@ namespace WebDAVSharp.Server.MethodHandlers /// internal class WebDavLockMethodHandler : WebDavMethodHandlerBase, IWebDavMethodHandler { + + #region Properties + /// /// Gets the collection of the names of the HTTP methods handled by this instance. /// @@ -35,6 +40,10 @@ public IEnumerable Names } } + #endregion + + #region Functions + /// /// Processes the request. /// @@ -46,7 +55,7 @@ public IEnumerable Names /// public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IWebDavStore store) { - ILog log = LogManager.GetCurrentClassLogger(); + /*************************************************************************************************** * Retreive al the information from the request @@ -55,26 +64,109 @@ public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IW // read the headers int depth = GetDepthHeader(context.Request); string timeout = GetTimeoutHeader(context.Request); - + string locktoken = GetLockTokenIfHeader(context.Request); + int lockResult; // Initiate the XmlNamespaceManager and the XmlNodes - XmlNamespaceManager manager = null; - XmlNode lockscopeNode = null, locktypeNode = null, ownerNode = null; + XmlNamespaceManager manager; + XmlNode lockscopeNode, locktypeNode, ownerNode; + XmlDocument requestDocument = new XmlDocument(); - // try to read the body - try + if (string.IsNullOrEmpty(locktoken)) { - StreamReader reader = new StreamReader(context.Request.InputStream, Encoding.UTF8); - string requestBody = reader.ReadToEnd(); + #region New Lock + // try to read the body + try + { + StreamReader reader = new StreamReader(context.Request.InputStream, Encoding.UTF8); + string requestBody = reader.ReadToEnd(); + + if (!requestBody.Equals("") && requestBody.Length != 0) + { + + requestDocument.LoadXml(requestBody); + + if (requestDocument.DocumentElement != null && + requestDocument.DocumentElement.LocalName != "prop" && + requestDocument.DocumentElement.LocalName != "lockinfo") + { + WebDavServer.Log.Debug("LOCK method without prop or lockinfo element in xml document"); + } + + manager = new XmlNamespaceManager(requestDocument.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:"); + + // Get the lockscope, locktype and owner as XmlNodes from the XML document + lockscopeNode = requestDocument.DocumentElement.SelectSingleNode("D:lockscope", manager); + locktypeNode = requestDocument.DocumentElement.SelectSingleNode("D:locktype", manager); + ownerNode = requestDocument.DocumentElement.SelectSingleNode("D:owner", manager); + } + else + { + throw new WebDavPreconditionFailedException(); + } + } + catch (Exception ex) + { + WebDavServer.Log.Warn(ex.Message); + throw; + } - if (!requestBody.Equals("") && requestBody.Length != 0) + + /*************************************************************************************************** + * Lock the file or folder + ***************************************************************************************************/ + + + // Get the parent collection of the item + IWebDavStoreCollection collection = GetParentCollection(server, store, context.Request.Url); + + WebDavLockScope lockscope = (lockscopeNode.InnerXml.StartsWith(""; + const string responseXml = ""; responseDoc.LoadXml(responseXml); // Select the activelock XmlNode @@ -142,41 +209,33 @@ public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IW // Add the additional elements, e.g. the header elements // The timeout element - WebDavProperty timeoutProperty = new WebDavProperty("timeout", timeout); + WebDavProperty timeoutProperty = new WebDavProperty("timeout", timeout);// timeout); activelock.AppendChild(timeoutProperty.ToXmlElement(responseDoc)); // The depth element WebDavProperty depthProperty = new WebDavProperty("depth", (depth == 0 ? "0" : "Infinity")); activelock.AppendChild(depthProperty.ToXmlElement(responseDoc)); - + // The locktoken element - WebDavProperty locktokenProperty = new WebDavProperty("locktoken", ""); + WebDavProperty locktokenProperty = new WebDavProperty("locktoken", string.Empty); XmlElement locktokenElement = locktokenProperty.ToXmlElement(responseDoc); - WebDavProperty hrefProperty = new WebDavProperty("href", "opaquelocktoken:e71d4fae-5dec-22df-fea5-00a0c93bd5eb1"); + WebDavProperty hrefProperty = new WebDavProperty("href", locktoken);//"opaquelocktoken:e71d4fae-5dec-22df-fea5-00a0c93bd5eb1"); locktokenElement.AppendChild(hrefProperty.ToXmlElement(responseDoc)); + + activelock.AppendChild(locktokenElement); /*************************************************************************************************** * Send the response ***************************************************************************************************/ - + // convert the StringBuilder string resp = responseDoc.InnerXml; byte[] responseBytes = Encoding.UTF8.GetBytes(resp); - if (isNew) - { - // HttpStatusCode doesn't contain WebDav status codes, but HttpWorkerRequest can handle these WebDav status codes - context.Response.StatusCode = (int)HttpStatusCode.Created; - context.Response.StatusDescription = HttpWorkerRequest.GetStatusDescription((int)HttpStatusCode.Created); - } - else - { - // HttpStatusCode doesn't contain WebDav status codes, but HttpWorkerRequest can handle these WebDav status codes - context.Response.StatusCode = (int)HttpStatusCode.OK; - context.Response.StatusDescription = HttpWorkerRequest.GetStatusDescription((int)HttpStatusCode.OK); - } - + + context.Response.StatusCode = lockResult; + context.Response.StatusDescription = HttpWorkerRequest.GetStatusDescription(lockResult); // set the headers of the response context.Response.ContentLength64 = responseBytes.Length; @@ -187,5 +246,8 @@ public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IW context.Response.Close(); } + + #endregion + } } \ No newline at end of file diff --git a/MethodHandlers/WebDAVMethodHandlerBase.cs b/MethodHandlers/WebDAVMethodHandlerBase.cs index e2d0e8e..fc8fe14 100644 --- a/MethodHandlers/WebDAVMethodHandlerBase.cs +++ b/MethodHandlers/WebDAVMethodHandlerBase.cs @@ -10,9 +10,16 @@ namespace WebDAVSharp.Server.MethodHandlers /// This is the base class for implementations. /// internal abstract class WebDavMethodHandlerBase - { + { + + #region Variables + private const int DepthInfinity = -1; + #endregion + + #region Static Functions + /// /// Get the parent collection from the requested /// . @@ -39,9 +46,9 @@ public static IWebDavStoreCollection GetParentCollection(WebDavServer server, IW } catch (UnauthorizedAccessException) { - throw new WebDavUnauthorizedException(); + throw new WebDavUnauthorizedException(); } - catch(WebDavNotFoundException) + catch (WebDavNotFoundException) { throw new WebDavConflictException(); } @@ -124,6 +131,18 @@ public static bool GetOverwriteHeader(IHttpListenerRequest request) // else, return false } + public static string GetLockTokenIfHeader(IHttpListenerRequest request) + { + //() + return request.Headers.AllKeys.Contains("If") ? request.Headers["If"].Substring(2, request.Headers["If"].Length-4) : string.Empty; + } + public static string GetLockTokenHeader(IHttpListenerRequest request) + { + if (!request.Headers.AllKeys.Contains("Lock-Token")) return string.Empty; + string token = request.Headers["Lock-Token"]; + return (token.Substring(1, token.Length - 2)); + } + /// /// Gets the Timeout header : Second-number /// @@ -159,5 +178,7 @@ public static Uri GetDestinationHeader(IHttpListenerRequest request) // else, throw exception throw new WebDavConflictException(); } + + #endregion } } diff --git a/MethodHandlers/WebDAVMethodHandlers.cs b/MethodHandlers/WebDAVMethodHandlers.cs index c35a3e2..91312a8 100644 --- a/MethodHandlers/WebDAVMethodHandlers.cs +++ b/MethodHandlers/WebDAVMethodHandlers.cs @@ -10,6 +10,8 @@ namespace WebDAVSharp.Server.MethodHandlers /// internal static class WebDavMethodHandlers { + #region Properties + private static readonly List _BuiltIn = new List(); /// @@ -30,6 +32,9 @@ public static IEnumerable BuiltIn } } + #endregion + + #region Static Functions /// /// Scans the WebDAV# assemblies for known /// types. @@ -47,5 +52,7 @@ from type in methodHandlerTypes _BuiltIn.AddRange(methodHandlerInstances); } + + #endregion } } \ No newline at end of file diff --git a/MethodHandlers/WebDAVMkColMethodHandler.cs b/MethodHandlers/WebDAVMkColMethodHandler.cs index 56cf302..52c882a 100644 --- a/MethodHandlers/WebDAVMkColMethodHandler.cs +++ b/MethodHandlers/WebDAVMkColMethodHandler.cs @@ -13,6 +13,7 @@ namespace WebDAVSharp.Server.MethodHandlers /// internal class WebDavMkColMethodHandler : WebDavMethodHandlerBase, IWebDavMethodHandler { + #region Properties /// /// Gets the collection of the names of the HTTP methods handled by this instance. /// @@ -30,6 +31,10 @@ public IEnumerable Names } } + #endregion + + #region Functions + /// /// Processes the request. /// @@ -55,7 +60,9 @@ public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IW collection.CreateCollection(collectionName); - context.SendSimpleResponse(HttpStatusCode.Created); + context.SendSimpleResponse((int)HttpStatusCode.Created); } + + #endregion } } \ No newline at end of file diff --git a/MethodHandlers/WebDAVMoveMethodHandler.cs b/MethodHandlers/WebDAVMoveMethodHandler.cs index de62d47..ddd062d 100644 --- a/MethodHandlers/WebDAVMoveMethodHandler.cs +++ b/MethodHandlers/WebDAVMoveMethodHandler.cs @@ -13,6 +13,7 @@ namespace WebDAVSharp.Server.MethodHandlers /// internal class WebDavMoveMethodHandler : WebDavMethodHandlerBase, IWebDavMethodHandler { + #region Properties /// /// Gets the collection of the names of the HTTP methods handled by this instance. /// @@ -30,6 +31,10 @@ public IEnumerable Names } } + #endregion + + #region Functions + /// /// Processes the request. /// @@ -40,7 +45,7 @@ public IEnumerable Names /// The that the is hosting. public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IWebDavStore store) { - var source = context.Request.Url.GetItem(server, store); + IWebDavStoreItem source = context.Request.Url.GetItem(server, store); MoveItem(server, context, store, source); } @@ -56,7 +61,7 @@ public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IW /// The that will be moved /// If the source path is the same as the destination path /// If one of the preconditions failed - private void MoveItem(WebDavServer server, IHttpListenerContext context, IWebDavStore store, + private static void MoveItem(WebDavServer server, IHttpListenerContext context, IWebDavStore store, IWebDavStoreItem sourceWebDavStoreItem) { Uri destinationUri = GetDestinationHeader(context.Request); @@ -81,7 +86,9 @@ private void MoveItem(WebDavServer server, IHttpListenerContext context, IWebDav destinationParentCollection.MoveItemHere(sourceWebDavStoreItem, destinationName); // send correct response - context.SendSimpleResponse(isNew ? HttpStatusCode.Created : HttpStatusCode.NoContent); + context.SendSimpleResponse(isNew ? (int)HttpStatusCode.Created : (int)HttpStatusCode.NoContent); } + + #endregion } } \ No newline at end of file diff --git a/MethodHandlers/WebDAVOptionsMethodHandler.cs b/MethodHandlers/WebDAVOptionsMethodHandler.cs index 5bceab4..b088c5f 100644 --- a/MethodHandlers/WebDAVOptionsMethodHandler.cs +++ b/MethodHandlers/WebDAVOptionsMethodHandler.cs @@ -9,6 +9,16 @@ namespace WebDAVSharp.Server.MethodHandlers /// internal class WebDavOptionsMethodHandler : WebDavMethodHandlerBase, IWebDavMethodHandler { + #region Variables + + private static readonly List verbsAllowed = new List { "OPTIONS", "TRACE", "GET", "HEAD", "POST", "COPY", "PROPFIND", "LOCK", "UNLOCK" }; + + private static readonly List verbsPublic = new List { "OPTIONS", "GET", "HEAD", "PROPFIND", "PROPPATCH", "MKCOL", "PUT", "DELETE", "COPY", "MOVE", "LOCK", "UNLOCK" }; + + #endregion + + #region Properties + /// /// Gets the collection of the names of the HTTP methods handled by this instance. /// @@ -23,6 +33,9 @@ public IEnumerable Names } } + #endregion + + #region Functions /// /// Processes the request. /// @@ -33,9 +46,6 @@ public IEnumerable Names /// The that the is hosting. public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IWebDavStore store) { - List verbsAllowed = new List { "OPTIONS", "TRACE", "GET", "HEAD", "POST", "COPY", "PROPFIND", "LOCK", "UNLOCK" }; - - List verbsPublic = new List { "OPTIONS", "GET", "HEAD", "PROPFIND", "PROPPATCH", "MKCOL", "PUT", "DELETE", "COPY", "MOVE", "LOCK", "UNLOCK" }; foreach (string verb in verbsAllowed) context.Response.AppendHeader("Allow", verb); @@ -46,5 +56,7 @@ public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IW // Sends 200 OK context.SendSimpleResponse(); } + + #endregion } } \ No newline at end of file diff --git a/MethodHandlers/WebDAVPropfindMethodHandler.cs b/MethodHandlers/WebDAVPropfindMethodHandler.cs index 30a4ead..6a40d71 100644 --- a/MethodHandlers/WebDAVPropfindMethodHandler.cs +++ b/MethodHandlers/WebDAVPropfindMethodHandler.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using System.Configuration; +using System.Globalization; using System.IO; +using System.Linq; using System.Text; using System.Web; using System.Xml; @@ -8,6 +11,7 @@ using WebDAVSharp.Server.Adapters; using WebDAVSharp.Server.Exceptions; using WebDAVSharp.Server.Stores; +using WebDAVSharp.Server.Stores.Locks; using WebDAVSharp.Server.Utilities; namespace WebDAVSharp.Server.MethodHandlers @@ -17,11 +21,32 @@ namespace WebDAVSharp.Server.MethodHandlers /// internal class WebDavPropfindMethodHandler : WebDavMethodHandlerBase, IWebDavMethodHandler { - private ILog _log; + #region Variables + private Uri _requestUri; private List _requestedProperties; private List _webDavStoreItems; + private static List _list = new List + { + 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") , + //new WebDavProperty("getcontentlanguage"), + //new WebDavProperty("lockdiscovery") + }; + + + #endregion + + #region Properties + /// /// Gets the collection of the names of the HTTP methods handled by this instance. /// @@ -39,6 +64,12 @@ public IEnumerable Names } } + internal IHttpListenerContext _Context; + + #endregion + + #region Functions + /// /// Processes the request. /// @@ -50,8 +81,7 @@ public IEnumerable Names /// public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IWebDavStore store) { - _log = LogManager.GetCurrentClassLogger(); - + _Context = context; /*************************************************************************************************** * Retreive all the information from the request ***************************************************************************************************/ @@ -77,12 +107,12 @@ public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IW if (requestDoc.DocumentElement != null) { if (requestDoc.DocumentElement.LocalName != "propfind") - _log.Debug("PROPFIND method without propfind in xml document"); + WebDavServer.Log.Debug("PROPFIND method without propfind in xml document"); else { XmlNode n = requestDoc.DocumentElement.FirstChild; if (n == null) - _log.Debug("propfind element without children"); + WebDavServer.Log.Debug("propfind element without children"); else { switch (n.LocalName) @@ -151,7 +181,6 @@ private static Uri GetRequestUri(string uri) /// private static List GetWebDavStoreItems(IWebDavStoreItem iWebDavStoreItem, int depth) { - ILog _log = LogManager.GetCurrentClassLogger(); List list = new List(); //IWebDavStoreCollection @@ -162,17 +191,10 @@ private static List GetWebDavStoreItems(IWebDavStoreItem iWebD 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); - } - } + + foreach (IWebDavStoreItem item in collection.Items.Where(item => !list.Contains(item))) + list.Add(item); + return list; } // if the item is not a document, throw conflict exception @@ -195,7 +217,7 @@ private static List GetWebDavStoreItems(IWebDavStoreItem iWebD /// /// The that contains the request body /// - private XmlDocument GetXmlDocument(IHttpListenerRequest request) + private static XmlDocument GetXmlDocument(IHttpListenerRequest request) { try { @@ -205,14 +227,14 @@ private XmlDocument GetXmlDocument(IHttpListenerRequest request) if (!String.IsNullOrEmpty(requestBody)) { - var xmlDocument = new XmlDocument(); + XmlDocument xmlDocument = new XmlDocument(); xmlDocument.LoadXml(requestBody); return xmlDocument; } } catch (Exception) { - _log.Warn("XmlDocument has not been read correctly"); + WebDavServer.Log.Warn("XmlDocument has not been read correctly"); } return new XmlDocument(); @@ -224,23 +246,9 @@ private XmlDocument GetXmlDocument(IHttpListenerRequest request) /// /// The list with all the /// - private List GetAllProperties() + private static List GetAllProperties() { - List list = new List - { - 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; + return _list; } #endregion @@ -274,14 +282,14 @@ private XmlDocument ResponseDocument(IHttpListenerContext context, bool propname foreach (IWebDavStoreItem webDavStoreItem in _webDavStoreItems) { // Create the response element - WebDavProperty responseProperty = new WebDavProperty("response", ""); + WebDavProperty responseProperty = new WebDavProperty("response", string.Empty); XmlElement responseElement = responseProperty.ToXmlElement(responseDoc); // The href element Uri result; if (count == 0) { - Uri.TryCreate(_requestUri, "", out result); + Uri.TryCreate(_requestUri, string.Empty, out result); } else { @@ -292,17 +300,24 @@ private XmlDocument ResponseDocument(IHttpListenerContext context, bool propname count++; // The propstat element - WebDavProperty propstatProperty = new WebDavProperty("propstat", ""); + WebDavProperty propstatProperty = new WebDavProperty("propstat", string.Empty); XmlElement propstatElement = propstatProperty.ToXmlElement(responseDoc); // The prop element - WebDavProperty propProperty = new WebDavProperty("prop", ""); + WebDavProperty propProperty = new WebDavProperty("prop", string.Empty); XmlElement propElement = propProperty.ToXmlElement(responseDoc); - foreach (WebDavProperty davProperty in _requestedProperties) - { + //All properties but lockdiscovery and supportedlock can be handled here. + foreach (WebDavProperty davProperty in _requestedProperties.Where(d => d.Name != "lockdiscovery" && d.Name != "supportedlock")) propElement.AppendChild(PropChildElement(davProperty, responseDoc, webDavStoreItem, propname)); - } + + //Since lockdiscovery returns an xml tree, we need to process it seperately. + if (_requestedProperties.FirstOrDefault(d => d.Name == "lockdiscovery") != null) + propElement.AppendChild(LockDiscovery(result, ref responseDoc)); + + //Since supportedlock returns an xml tree, we need to process it seperately. + if (_requestedProperties.FirstOrDefault(d => d.Name == "supportedlock") != null) + propElement.AppendChild(SupportedLocks(ref responseDoc)); // Add the prop element to the propstat element propstatElement.AppendChild(propElement); @@ -337,7 +352,7 @@ private XmlDocument ResponseDocument(IHttpListenerContext context, bool propname /// /// The of the containing a value or child elements /// - private XmlElement PropChildElement(WebDavProperty webDavProperty, XmlDocument xmlDocument, IWebDavStoreItem iWebDavStoreItem, bool isPropname) + private static XmlElement PropChildElement(WebDavProperty webDavProperty, XmlDocument xmlDocument, IWebDavStoreItem iWebDavStoreItem, bool isPropname) { // If Propfind request contains a propname element if (isPropname) @@ -345,17 +360,19 @@ private XmlElement PropChildElement(WebDavProperty webDavProperty, XmlDocument x 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 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", ""); + WebDavProperty collectionProperty = new WebDavProperty("collection", String.Empty); xmlElement.AppendChild(collectionProperty.ToXmlElement(xmlDocument)); return xmlElement; } @@ -368,7 +385,7 @@ private XmlElement PropChildElement(WebDavProperty webDavProperty, XmlDocument x /// /// A containing the value /// - private string GetWebDavPropertyValue(IWebDavStoreItem webDavStoreItem, WebDavProperty davProperty) + private static string GetWebDavPropertyValue(IWebDavStoreItem webDavStoreItem, WebDavProperty davProperty) { switch (davProperty.Name) { @@ -377,32 +394,149 @@ private string GetWebDavPropertyValue(IWebDavStoreItem webDavStoreItem, WebDavPr case "displayname": return webDavStoreItem.Name; case "getcontentlanguage": - // still to implement !!! + //todo getcontentlanguage return String.Empty; case "getcontentlength": - return (!webDavStoreItem.IsCollection ? "" + ((IWebDavStoreDocument) webDavStoreItem).Size : ""); + return (!webDavStoreItem.IsCollection ? "" + ((IWebDavStoreDocument)webDavStoreItem).Size : string.Empty); case "getcontenttype": - return (!webDavStoreItem.IsCollection ? "" + ((IWebDavStoreDocument) webDavStoreItem).MimeType : ""); + return (!webDavStoreItem.IsCollection ? "" + ((IWebDavStoreDocument)webDavStoreItem).MimeType : string.Empty); case "getetag": - return (!webDavStoreItem.IsCollection ? "" + ((IWebDavStoreDocument) webDavStoreItem).Etag : ""); + return (!webDavStoreItem.IsCollection ? "" + ((IWebDavStoreDocument)webDavStoreItem).Etag : string.Empty); case "getlastmodified": return webDavStoreItem.ModificationDate.ToUniversalTime().ToString("R"); - case "lockdiscovery": - // still to implement !!! - return String.Empty; case "resourcetype": + //todo Add resourceType return ""; - case "supportedlock": - // still to implement !!! - return ""; - //webDavProperty.Value = ""; case "ishidden": - return "" + webDavStoreItem.Hidden; + return webDavStoreItem.Hidden.ToString(CultureInfo.InvariantCulture); default: return String.Empty; } } + /// + /// Returns an XML Fragment which details the supported locks on this implementation. + /// 15.10 supportedlock Property + /// Name: + /// supportedlock + /// Purpose: + /// To provide a listing of the lock capabilities supported by the resource. + /// Protected: + /// MUST be protected. Servers, not clients, determine what lock mechanisms are supported. + /// COPY/MOVE behavior: + /// This property value is dependent on the kind of locks supported at the destination, not on the value of the property at the source resource. Servers attempting to COPY to a destination should not attempt to set this property at the destination. + /// Description: + /// Returns a listing of the combinations of scope and access types that may be specified in a lock request on the resource. Note that the actual contents are themselves controlled by access controls, so a server is not required to provide information the client is not authorized to see. This property is NOT lockable with respect to write locks (Section 7). + /// + /// + /// + private static XmlNode SupportedLocks(ref XmlDocument responsedoc) + { + XmlNode node = new WebDavProperty("supportedlock").ToXmlElement(responsedoc); + + XmlNode lockentry = new WebDavProperty("lockentry").ToXmlElement(responsedoc); + node.AppendChild(lockentry); + + XmlNode lockscope = new WebDavProperty("lockscope").ToXmlElement(responsedoc); + lockentry.AppendChild(lockscope); + + XmlNode exclusive = new WebDavProperty("exclusive").ToXmlElement(responsedoc); + lockscope.AppendChild(exclusive); + + XmlNode locktype = new WebDavProperty("locktype").ToXmlElement(responsedoc); + lockentry.AppendChild(locktype); + + XmlNode write = new WebDavProperty("write").ToXmlElement(responsedoc); + locktype.AppendChild(write); + + XmlNode lockentry1 = new WebDavProperty("lockentry").ToXmlElement(responsedoc); + node.AppendChild(lockentry1); + + XmlNode lockscope1 = new WebDavProperty("lockscope").ToXmlElement(responsedoc); + lockentry1.AppendChild(lockscope1); + + XmlNode shared = new WebDavProperty("shared").ToXmlElement(responsedoc); + lockscope1.AppendChild(shared); + + XmlNode locktype1 = new WebDavProperty("locktype").ToXmlElement(responsedoc); + lockentry1.AppendChild(locktype1); + + XmlNode write1 = new WebDavProperty("write").ToXmlElement(responsedoc); + locktype1.AppendChild(write1); + + return node; + } + + /// + /// Returns the XML Format according to RFC + /// Name: + /// lockdiscovery + /// Purpose: + /// Describes the active locks on a resource + /// Protected: + /// MUST be protected. Clients change the list of locks through LOCK and UNLOCK, not through PROPPATCH. + /// COPY/MOVE behavior: + /// The value of this property depends on the lock state of the destination, not on the locks of the source resource. Recall + /// that locks are not moved in a MOVE operation. + /// Description: + /// Returns a listing of who has a lock, what type of lock he has, the timeout type and the time remaining on the timeout, + /// and the associated lock token. Owner information MAY be omitted if it is considered sensitive. If there are no locks, but + /// the server supports locks, the property will be present but contain zero 'activelock' elements. If there are one or more locks, + /// an 'activelock' element appears for each lock on the resource. This property is NOT lockable with respect to write locks (Section 7). + /// + /// + /// + /// + private static XmlNode LockDiscovery(Uri path, ref XmlDocument responsedoc) + { + XmlNode node = new WebDavProperty("lockdiscovery").ToXmlElement(responsedoc); + foreach (var ilock in WebDavStoreItemLock.GetLocks(path)) + { + XmlNode activelock = new WebDavProperty("activelock").ToXmlElement(responsedoc); + node.AppendChild(activelock); + + XmlNode locktype = new WebDavProperty("locktype").ToXmlElement(responsedoc); + activelock.AppendChild(locktype); + + XmlNode locktypeitem = new WebDavProperty(ilock.LockType.ToString().ToLower()).ToXmlElement(responsedoc); + locktype.AppendChild(locktypeitem); + + XmlNode lockscope = new WebDavProperty("lockscope").ToXmlElement(responsedoc); + activelock.AppendChild(lockscope); + + XmlNode lockscopeitem = new WebDavProperty(ilock.LockScope.ToString().ToLower()).ToXmlElement(responsedoc); + lockscope.AppendChild(lockscopeitem); + + XmlNode depth = new WebDavProperty("depth").ToXmlElement(responsedoc); + depth.InnerText = ilock.Depth.ToString(CultureInfo.InvariantCulture); + activelock.AppendChild(depth); + + XmlNode owner = new WebDavProperty("owner").ToXmlElement(responsedoc); + owner.InnerText = ilock.Owner; + activelock.AppendChild(owner); + + XmlNode timeout = new WebDavProperty("timeout").ToXmlElement(responsedoc); + timeout.InnerText = ilock.RequestedTimeout; + activelock.AppendChild(timeout); + + XmlNode locktoken = new WebDavProperty("locktoken").ToXmlElement(responsedoc); + activelock.AppendChild(locktoken); + + XmlNode tokenhref = new WebDavProperty("href").ToXmlElement(responsedoc); + tokenhref.InnerText = ilock.Token; + locktoken.AppendChild(tokenhref); + + XmlNode lockroot = new WebDavProperty("lockroot").ToXmlElement(responsedoc); + activelock.AppendChild(lockroot); + + XmlNode lockroothref = new WebDavProperty("href").ToXmlElement(responsedoc); + lockroothref.InnerText = ilock.Path.ToString(); + lockroot.AppendChild(lockroothref); + } + + return node; + } + #endregion #region SendResponse @@ -412,7 +546,7 @@ private string GetWebDavPropertyValue(IWebDavStoreItem webDavStoreItem, WebDavPr /// /// The containing the response /// The containing the response body - private static void SendResponse(IHttpListenerContext context, XmlDocument responseDocument) + private static void SendResponse(IHttpListenerContext context, XmlNode responseDocument) { // convert the XmlDocument byte[] responseBytes = Encoding.UTF8.GetBytes(responseDocument.InnerXml); @@ -430,5 +564,7 @@ private static void SendResponse(IHttpListenerContext context, XmlDocument respo #endregion + #endregion + } } diff --git a/MethodHandlers/WebDAVProppatchMethodHandler.cs b/MethodHandlers/WebDAVProppatchMethodHandler.cs index 57be08d..56d5c23 100644 --- a/MethodHandlers/WebDAVProppatchMethodHandler.cs +++ b/MethodHandlers/WebDAVProppatchMethodHandler.cs @@ -17,6 +17,8 @@ namespace WebDAVSharp.Server.MethodHandlers /// internal class WebDavProppatchMethodHandler : WebDavMethodHandlerBase, IWebDavMethodHandler { + + #region Properties /// /// Gets the collection of the names of the HTTP methods handled by this instance. /// @@ -34,6 +36,10 @@ public IEnumerable Names } } + #endregion + + #region Functions + /// /// Processes the request. /// @@ -44,8 +50,6 @@ public IEnumerable Names /// The that the is hosting. public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IWebDavStore store) { - ILog log = LogManager.GetCurrentClassLogger(); - /*************************************************************************************************** * Retreive al the information from the request ***************************************************************************************************/ @@ -72,7 +76,7 @@ public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IW { if (requestDocument.DocumentElement.LocalName != "propertyupdate") { - log.Debug("PROPPATCH method without propertyupdate element in xml document"); + WebDavServer.Log.Debug("PROPPATCH method without propertyupdate element in xml document"); } manager = new XmlNamespaceManager(requestDocument.NameTable); @@ -87,7 +91,7 @@ public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IW } catch (Exception ex) { - log.Warn(ex.Message); + WebDavServer.Log.Warn(ex.Message); } /*************************************************************************************************** @@ -118,6 +122,7 @@ public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IW fileInfo.LastWriteTime = Convert.ToDateTime(node.InnerText).ToUniversalTime(); break; case "Win32FileAttributes": + //todo Win32FileAttributes //fileInfo.Attributes = //fileInfo.Attributes = Convert.ToDateTime(node.InnerText); break; @@ -147,7 +152,7 @@ public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IW responseNode.AppendChild(hrefProperty.ToXmlElement(responseDoc)); // The propstat element - WebDavProperty propstatProperty = new WebDavProperty("propstat", ""); + WebDavProperty propstatProperty = new WebDavProperty("propstat", string.Empty); XmlElement propstatElement = propstatProperty.ToXmlElement(responseDoc); // The propstat/status element @@ -163,9 +168,9 @@ where child.Name.ToLower() .Contains("lastaccesstime") || child.Name.ToLower() .Contains("lastmodifiedtime") let node = propNode.SelectSingleNode(child.Name, manager) - select node != null - ? new WebDavProperty(child.LocalName, "", node.NamespaceURI) - : new WebDavProperty(child.LocalName, "", "")) + + select new WebDavProperty(child.LocalName, string.Empty, node != null ? node.NamespaceURI : string.Empty)) + propstatElement.AppendChild(property.ToXmlElement(responseDoc)); responseNode.AppendChild(propstatElement); @@ -192,5 +197,8 @@ where child.Name.ToLower() context.Response.Close(); } + + #endregion + } } \ No newline at end of file diff --git a/MethodHandlers/WebDAVPutMethodHandler.cs b/MethodHandlers/WebDAVPutMethodHandler.cs index 530561a..e548885 100644 --- a/MethodHandlers/WebDAVPutMethodHandler.cs +++ b/MethodHandlers/WebDAVPutMethodHandler.cs @@ -14,6 +14,8 @@ namespace WebDAVSharp.Server.MethodHandlers /// internal class WebDavPutMethodHandler : WebDavMethodHandlerBase, IWebDavMethodHandler { + #region Properties + /// /// Gets the collection of the names of the HTTP methods handled by this instance. /// @@ -31,6 +33,10 @@ public IEnumerable Names } } + #endregion + + #region Functions + /// /// Processes the request. /// @@ -79,7 +85,9 @@ public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IW } } - context.SendSimpleResponse(HttpStatusCode.Created); + context.SendSimpleResponse((int)HttpStatusCode.Created); } + + #endregion } } \ No newline at end of file diff --git a/MethodHandlers/WebDAVUnlockMethodHandler.cs b/MethodHandlers/WebDAVUnlockMethodHandler.cs index 5a42e00..c1a3412 100644 --- a/MethodHandlers/WebDAVUnlockMethodHandler.cs +++ b/MethodHandlers/WebDAVUnlockMethodHandler.cs @@ -1,7 +1,12 @@ using System.Collections.Generic; +using System.IO; using System.Net; +using System.Security.Principal; +using System.Text; +using System.Threading; using WebDAVSharp.Server.Adapters; using WebDAVSharp.Server.Stores; +using WebDAVSharp.Server.Stores.Locks; namespace WebDAVSharp.Server.MethodHandlers { @@ -10,6 +15,9 @@ namespace WebDAVSharp.Server.MethodHandlers /// internal class WebDavUnlockMethodHandler : WebDavMethodHandlerBase, IWebDavMethodHandler { + + #region Properties + /// /// Gets the collection of the names of the HTTP methods handled by this instance. /// @@ -27,6 +35,9 @@ public IEnumerable Names } } + #endregion + + #region Functions /// /// Processes the request. /// @@ -37,17 +48,14 @@ public IEnumerable Names /// The that the is hosting. public void ProcessRequest(WebDavServer server, IHttpListenerContext context, IWebDavStore store) { - // Get the parent collection of the item - IWebDavStoreCollection collection = GetParentCollection(server, store, context.Request.Url); - - // Get the item from the collection - IWebDavStoreItem item = GetItemFromCollection(collection, context.Request.Url); - /*************************************************************************************************** * Send the response ***************************************************************************************************/ - - context.SendSimpleResponse(HttpStatusCode.NoContent); + WindowsIdentity Identity = (WindowsIdentity)Thread.GetData(Thread.GetNamedDataSlot(WebDavServer.HttpUser)); + context.SendSimpleResponse(WebDavStoreItemLock.UnLock(context.Request.Url, GetLockTokenHeader(context.Request), Identity.Name)); } + + #endregion + } } \ No newline at end of file diff --git a/Stores/BaseClasses/WebDAVStoreBase.cs b/Stores/BaseClasses/WebDAVStoreBase.cs index 6fb93bd..bfedf30 100644 --- a/Stores/BaseClasses/WebDAVStoreBase.cs +++ b/Stores/BaseClasses/WebDAVStoreBase.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; namespace WebDAVSharp.Server.Stores.BaseClasses { @@ -7,7 +8,14 @@ namespace WebDAVSharp.Server.Stores.BaseClasses /// public abstract class WebDavStoreBase : IWebDavStore { + + #region Variables + private readonly IWebDavStoreCollection _root; + + #endregion + + #region Constructor /// /// Initializes a new instance of the class. @@ -23,7 +31,9 @@ protected WebDavStoreBase(IWebDavStoreCollection root) _root = root; } - #region IWebDAVStore Members + #endregion + + #region Properties /// /// Gets the root collection of this . @@ -37,5 +47,6 @@ public IWebDavStoreCollection Root } #endregion + } } \ No newline at end of file diff --git a/Stores/BaseClasses/WebDAVStoreDocumentBase.cs b/Stores/BaseClasses/WebDAVStoreDocumentBase.cs index 0c193d3..254ec48 100644 --- a/Stores/BaseClasses/WebDAVStoreDocumentBase.cs +++ b/Stores/BaseClasses/WebDAVStoreDocumentBase.cs @@ -8,6 +8,8 @@ namespace WebDAVSharp.Server.Stores.BaseClasses /// public class WebDavStoreDocumentBase : WebDavStoreItemBase { + + #region Constructor /// /// Initializes a new instance of the class. /// @@ -18,7 +20,9 @@ protected WebDavStoreDocumentBase(IWebDavStoreCollection parentCollection, strin { } - #region IWebDAVStoreItem Members + #endregion + + #region Properties /// /// Gets or sets the mime type of this . @@ -35,5 +39,6 @@ public string MimeType } #endregion + } } \ No newline at end of file diff --git a/Stores/BaseClasses/WebDAVStoreItemBase.cs b/Stores/BaseClasses/WebDAVStoreItemBase.cs index 3feb044..beec877 100644 --- a/Stores/BaseClasses/WebDAVStoreItemBase.cs +++ b/Stores/BaseClasses/WebDAVStoreItemBase.cs @@ -1,5 +1,8 @@ using System; +using System.Net; +using System.Runtime.InteropServices; using WebDAVSharp.Server.Exceptions; +using WebDAVSharp.Server.Stores.Locks; namespace WebDAVSharp.Server.Stores.BaseClasses { @@ -8,9 +11,14 @@ namespace WebDAVSharp.Server.Stores.BaseClasses /// public class WebDavStoreItemBase : IWebDavStoreItem { + #region Variables + private readonly IWebDavStoreCollection _parentCollection; private string _name; + #endregion + + #region Constuctor /// /// Initializes a new instance of the class. /// @@ -27,7 +35,9 @@ protected WebDavStoreItemBase(IWebDavStoreCollection parentCollection, string na _name = name; } - #region IWebDAVStoreItem Members + #endregion + + #region Properties /// /// Gets the parent that owns this . @@ -54,7 +64,8 @@ public string Name set { string fixedName = (value ?? string.Empty).Trim(); - if (fixedName == _name) return; + if (fixedName == _name) + return; if (!OnNameChanging(_name, fixedName)) throw new WebDavForbiddenException(); string oldName = _name; @@ -90,7 +101,10 @@ public virtual DateTime ModificationDate /// public virtual string ItemPath { - get { return String.Empty; } + get + { + return String.Empty; + } } /// @@ -117,6 +131,8 @@ public int Hidden #endregion + #region Functions + /// /// Called before the name of this is changing. /// @@ -140,5 +156,7 @@ protected virtual bool OnNameChanging(string oldName, string newName) protected virtual void OnNameChanged(string oldName, string newName) { } + + #endregion } } \ No newline at end of file diff --git a/Stores/DiskStore/WebDAVDiskStore.cs b/Stores/DiskStore/WebDAVDiskStore.cs index 26a54a5..1c20b09 100644 --- a/Stores/DiskStore/WebDAVDiskStore.cs +++ b/Stores/DiskStore/WebDAVDiskStore.cs @@ -10,8 +10,14 @@ namespace WebDAVSharp.Server.Stores.DiskStore [DebuggerDisplay("Disk Store ({RootPath})")] public sealed class WebDavDiskStore : IWebDavStore { + + #region Variables + private readonly string _rootPath; + #endregion + + #region Constructor /// /// Initializes a new instance of the class. /// @@ -23,13 +29,16 @@ public sealed class WebDavDiskStore : IWebDavStore public WebDavDiskStore(string rootPath) { if (rootPath == null) - throw new ArgumentNullException(rootPath); + throw new ArgumentNullException("rootPath"); if (!Directory.Exists(rootPath)) throw new DirectoryNotFoundException(rootPath); _rootPath = rootPath; } + #endregion + + #region Properties /// /// Gets the root path for the folder that is hosted in this . /// @@ -44,8 +53,6 @@ public string RootPath } } - #region IWebDAVStore Members - /// /// Gets the root collection of this . /// @@ -58,5 +65,6 @@ public IWebDavStoreCollection Root } #endregion + } } \ No newline at end of file diff --git a/Stores/DiskStore/WebDAVDiskStoreCollection.cs b/Stores/DiskStore/WebDAVDiskStoreCollection.cs index 7faffa9..900dc86 100644 --- a/Stores/DiskStore/WebDAVDiskStoreCollection.cs +++ b/Stores/DiskStore/WebDAVDiskStoreCollection.cs @@ -15,8 +15,14 @@ namespace WebDAVSharp.Server.Stores.DiskStore [DebuggerDisplay("Directory ({Name})")] public sealed class WebDavDiskStoreCollection : WebDavDiskStoreItem, IWebDavStoreCollection { + #region Variables + private readonly Dictionary _items = new Dictionary(); + #endregion + + #region Constructor + /// /// Initializes a new instance of the class. /// @@ -28,6 +34,8 @@ public WebDavDiskStoreCollection(WebDavDiskStoreCollection parentCollection, str } + #endregion + #region IWebDAVStoreCollection Members /// @@ -38,11 +46,7 @@ public IEnumerable Items { get { - HashSet toDelete = new HashSet(_items.Values); List items = new List(); - - // TODO: Refactor to get rid of duplicate loop code - List directories = new List(); try { // Impersonate the current user and get all the directories @@ -53,10 +57,28 @@ public IEnumerable Items try { bool canread = CanReadDirectory(Path.Combine(ItemPath, dirName)); - if (canread) + if (!canread) + continue; + string name = Path.GetFileName(dirName); + if (String.IsNullOrEmpty(name)) + continue; + WebDavDiskStoreCollection collection = null; + + WeakReference wr; + if (_items.TryGetValue(name, out wr)) { - directories.Add(dirName); + collection = wr.Target as WebDavDiskStoreCollection; + if (collection == null) + continue; } + + if (collection == null) + { + collection = new WebDavDiskStoreCollection(this, dirName); + _items[name] = new WeakReference(collection); + } + + items.Add(collection); } catch (Exception) { @@ -64,65 +86,35 @@ public IEnumerable Items } } } - } - catch - { - throw new WebDavUnauthorizedException(); - } - foreach (string subDirectoryPath in directories) - { - string name = Path.GetFileName(subDirectoryPath); - WebDavDiskStoreCollection collection = null; - WeakReference wr; - if (_items.TryGetValue(name, out wr)) - { - collection = wr.Target as WebDavDiskStoreCollection; - if (collection == null) - toDelete.Remove(wr); - } - if (collection == null) + foreach (string filePath in (Directory.GetFiles(ItemPath).Where(fileName => CanReadFile(Path.Combine(ItemPath, fileName))))) { - collection = new WebDavDiskStoreCollection(this, subDirectoryPath); - _items[name] = new WeakReference(collection); - } + string name = Path.GetFileName(filePath); + WebDavDiskStoreDocument document = null; + if (string.IsNullOrEmpty(name)) + continue; - items.Add(collection); - } - List files = new List(); - try - { - // Impersonate the current user and get all the files - using (Identity.Impersonate()) - { - files.AddRange(Directory.GetFiles(ItemPath).Where(fileName => CanReadFile(Path.Combine(ItemPath, fileName)))); + WeakReference wr; + if (_items.TryGetValue(name, out wr)) + { + document = wr.Target as WebDavDiskStoreDocument; + if (document == null) + continue; + } + + if (document == null) + { + document = new WebDavDiskStoreDocument(this, filePath); + _items[name] = new WeakReference(document); + } + items.Add(document); } } catch { throw new WebDavUnauthorizedException(); } - foreach (string filePath in files) - { - string name = Path.GetFileName(filePath); - WebDavDiskStoreDocument document = null; - - WeakReference wr; - if (_items.TryGetValue(name, out wr)) - { - document = wr.Target as WebDavDiskStoreDocument; - if (document == null) - toDelete.Remove(wr); - } - - if (document == null) - { - document = new WebDavDiskStoreDocument(this, filePath); - _items[name] = new WeakReference(document); - } - items.Add(document); - } return items.ToArray(); } } @@ -366,23 +358,23 @@ public IWebDavStoreItem CopyItemHere(IWebDavStoreItem source, string destination } - // We copy the file with an override. - try - { - // Impersonate the current user and copy the file - WindowsImpersonationContext wic = Identity.Impersonate(); - File.Copy(source.ItemPath, destinationItemPath, true); - wic.Undo(); - } - catch - { - throw new WebDavUnauthorizedException(); - } + // We copy the file with an override. + try + { + // Impersonate the current user and copy the file + WindowsImpersonationContext wic = Identity.Impersonate(); + File.Copy(source.ItemPath, destinationItemPath, true); + wic.Undo(); + } + catch + { + throw new WebDavUnauthorizedException(); + } - // We return the moved file as a WebDAVDiskStoreDocument - WebDavDiskStoreDocument document = new WebDavDiskStoreDocument(this, destinationItemPath); - _items.Add(destinationName, new WeakReference(document)); - return document; + // We return the moved file as a WebDAVDiskStoreDocument + WebDavDiskStoreDocument document = new WebDavDiskStoreDocument(this, destinationItemPath); + _items.Add(destinationName, new WeakReference(document)); + return document; } @@ -414,7 +406,8 @@ private static void DirectoryCopy(string sourceDirName, string destDirName, bool } // If copying subdirectories, copy them and their contents to new location. - if (!copySubDirs) return; + if (!copySubDirs) + return; // Get the files in the directory and copy them to the new location. FileInfo[] files = dir.GetFiles(); foreach (FileInfo file in files) @@ -443,11 +436,10 @@ private static void DirectoryCopy(string sourceDirName, string destDirName, bool public IWebDavStoreItem MoveItemHere(IWebDavStoreItem source, string destinationName) { // try to get the path of the source item - string sourceItemPath = ""; WebDavDiskStoreItem sourceItem = (WebDavDiskStoreItem)source; - sourceItemPath = sourceItem.ItemPath; + string sourceItemPath = sourceItem.ItemPath; - if (sourceItemPath.Equals("")) + if (sourceItemPath.Equals(string.Empty)) { throw new Exception("Path to the source item not defined."); } @@ -475,24 +467,24 @@ public IWebDavStoreItem MoveItemHere(IWebDavStoreItem source, string destination _items.Add(destinationName, new WeakReference(collection)); return collection; } - - try - { - // Impersonate the current user and move the file - WindowsImpersonationContext wic = Identity.Impersonate(); - File.Move(sourceItemPath, destinationItemPath); - wic.Undo(); - } - catch - { - throw new WebDavUnauthorizedException(); - } + + try + { + // Impersonate the current user and move the file + WindowsImpersonationContext wic = Identity.Impersonate(); + File.Move(sourceItemPath, destinationItemPath); + wic.Undo(); + } + catch + { + throw new WebDavUnauthorizedException(); + } // We return the moved file as a WebDAVDiskStoreDocument WebDavDiskStoreDocument document = new WebDavDiskStoreDocument(this, destinationItemPath); _items.Add(destinationName, new WeakReference(document)); return document; - + } #endregion diff --git a/Stores/DiskStore/WebDAVDiskStoreDocument.cs b/Stores/DiskStore/WebDAVDiskStoreDocument.cs index 73646a2..e382529 100644 --- a/Stores/DiskStore/WebDAVDiskStoreDocument.cs +++ b/Stores/DiskStore/WebDAVDiskStoreDocument.cs @@ -14,6 +14,8 @@ namespace WebDAVSharp.Server.Stores.DiskStore [DebuggerDisplay("File ({Name})")] public sealed class WebDavDiskStoreDocument : WebDavDiskStoreItem, IWebDavStoreDocument { + #region Constructor + /// /// Initializes a new instance of the class. /// @@ -31,7 +33,9 @@ public WebDavDiskStoreDocument(WebDavDiskStoreCollection parentCollection, strin // Do nothing here } - #region IWebDAVStoreDocument Members + #endregion + + #region Functions /// /// Gets the size of the document in bytes. @@ -67,11 +71,6 @@ public string Etag } } - - #endregion - - #region IWebDAVStoreDocument Members - /// /// Opens a object for the document, in read-only mode. /// diff --git a/Stores/DiskStore/WebDAVDiskStoreItem.cs b/Stores/DiskStore/WebDAVDiskStoreItem.cs index 8d52dd0..4d09096 100644 --- a/Stores/DiskStore/WebDAVDiskStoreItem.cs +++ b/Stores/DiskStore/WebDAVDiskStoreItem.cs @@ -2,7 +2,6 @@ using System.IO; using System.Security.Principal; using System.Threading; -using Common.Logging; using WebDAVSharp.Server.Stores.BaseClasses; namespace WebDAVSharp.Server.Stores.DiskStore @@ -17,18 +16,20 @@ namespace WebDAVSharp.Server.Stores.DiskStore /// public class WebDavDiskStoreItem : WebDavStoreItemBase { + + #region Variables + /// /// Gets the Identity of the person logged on via HTTP Request. /// protected readonly WindowsIdentity Identity; - /// - /// Log - /// - protected ILog Log; - private readonly WebDavDiskStoreCollection _parentCollection; private readonly string _path; + #endregion + + #region Constructor + /// /// Initializes a new instance of the class. /// @@ -41,17 +42,21 @@ public class WebDavDiskStoreItem : WebDavStoreItemBase /// The path that this maps to. /// path /// is null or empty. - protected WebDavDiskStoreItem(WebDavDiskStoreCollection parentCollection, string path) : base(parentCollection, path) + protected WebDavDiskStoreItem(WebDavDiskStoreCollection parentCollection, string path) + : base(parentCollection, path) { if (String.IsNullOrWhiteSpace(path)) throw new ArgumentNullException("path"); - - _parentCollection = parentCollection; _path = path; + Identity = (WindowsIdentity)Thread.GetData(Thread.GetNamedDataSlot(WebDavServer.HttpUser)); - Log = LogManager.GetCurrentClassLogger(); + } + #endregion + + #region Properties + /// /// Gets the path to this . /// @@ -63,8 +68,6 @@ public override string ItemPath } } - #region IWebDAVStoreItem Members - /// /// Gets or sets the name of this . /// @@ -137,5 +140,8 @@ public override DateTime ModificationDate } #endregion + + + } } \ No newline at end of file diff --git a/Stores/IWebDAVStoreItem.cs b/Stores/IWebDAVStoreItem.cs index 6d85551..f07eae5 100644 --- a/Stores/IWebDAVStoreItem.cs +++ b/Stores/IWebDAVStoreItem.cs @@ -1,4 +1,7 @@ using System; +using System.Net; +using System.Runtime.InteropServices; +using WebDAVSharp.Server.Stores.Locks; namespace WebDAVSharp.Server.Stores { @@ -88,5 +91,7 @@ int Hidden { get; } + + } } \ No newline at end of file diff --git a/Stores/Locks/WebDavLockScope.cs b/Stores/Locks/WebDavLockScope.cs new file mode 100644 index 0000000..90211dc --- /dev/null +++ b/Stores/Locks/WebDavLockScope.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WebDAVSharp.Server.Stores.Locks +{ + /// + /// Possible scopes for locks. + /// + public enum WebDavLockScope + { + /// + /// Can only have one exclusive Lock + /// + Exclusive, + + /// + /// Can be many. + /// + Shared + } +} diff --git a/Stores/Locks/WebDavLockType.cs b/Stores/Locks/WebDavLockType.cs new file mode 100644 index 0000000..dfcd69d --- /dev/null +++ b/Stores/Locks/WebDavLockType.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WebDAVSharp.Server.Stores.Locks +{ + /// + /// Specifies the access type of a lock. At present, this specification only defines one lock type, the write lock. + /// + public enum WebDavLockType + { + /// + /// A Write Lock type + /// + Write, + } +} diff --git a/Stores/Locks/WebDavStoreItemLock.cs b/Stores/Locks/WebDavStoreItemLock.cs new file mode 100644 index 0000000..d130238 --- /dev/null +++ b/Stores/Locks/WebDavStoreItemLock.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Xml; + + +namespace WebDAVSharp.Server.Stores.Locks +{ + /// + /// This class provides the locking functionality. + /// + /// + public class WebDavStoreItemLock + { + + #region Variables + /// + /// Allow Objects to be checked out forever + /// + internal static bool AllowInfiniteCheckouts = false; + + /// + /// Max amount of seconds a item can be checkout for. + /// + internal static long MaxCheckOutSeconds = long.MaxValue; + + /// + /// Used to store the locks per URI + /// + private static readonly Dictionary> ObjectLocks = new Dictionary>(); + + #endregion + + /// + /// This function removes any expired locks for the path. + /// + /// + private static void CleanLocks(Uri path) + { + lock (ObjectLocks) + { + if (!ObjectLocks.ContainsKey(path)) + return; + foreach ( + WebDaveStoreItemLockInstance ilock in ObjectLocks[path].ToList() + .Where(ilock => ilock.ExpirationDate != null && (DateTime)ilock.ExpirationDate < DateTime.Now) + ) + ObjectLocks[path].Remove(ilock); + } + } + + /// + /// This function will refresh an existing lock. + /// + /// Target URI to the file or folder + /// The token issued when the lock was established + /// The requested timeout + /// Output parameter, returns the Request document that was used when the lock was established. + /// + public static int RefreshLock(Uri path, string locktoken, ref string requestedlocktimeout, + out XmlDocument requestDocument) + { + CleanLocks(path); + //Refreshing an existing lock + + //If a lock doesn't exist then lets just reply with a Precondition Failed. + //412 (Precondition Failed), with 'lock-token-matches-request-uri' precondition code - The LOCK request was + //made with an If header, indicating that the client wishes to refresh the given lock. However, the Request-URI + //did not fall within the scope of the lock identified by the token. The lock may have a scope that does not + //include the Request-URI, or the lock could have disappeared, or the token may be invalid. + requestDocument = null; + if (!ObjectLocks.ContainsKey(path)) + return 412; + + string tmptoken = locktoken; + lock (ObjectLocks) + { + WebDaveStoreItemLockInstance ilock = ObjectLocks[path].FirstOrDefault(d => (d.Token == tmptoken)); + if (ilock == null) + { + WebDavServer.Log.Debug("Lock Refresh Failed , Lock does not exist."); + return 412; + } + WebDavServer.Log.Debug("Lock Refresh Successful."); + ilock.RefreshLock(ref requestedlocktimeout); + requestDocument = ilock.RequestDocument; + + return (int)HttpStatusCode.OK; + } + } + + /// + /// Locks the request Path. + /// + /// URI to the item to be locked + /// The lock Scope used for locking + /// The lock Type used for locking + /// The owner of the lock + /// The requested timeout + /// Out parameter, returns the issued token + /// the Request Document + /// How deep to lock, 0,1, or infinity + /// + public static int Lock(Uri path, WebDavLockScope lockscope, WebDavLockType locktype, string lockowner, + ref string requestedlocktimeout, out string locktoken, XmlDocument requestDocument, int depth) + { + CleanLocks(path); + WebDavServer.Log.Info("Lock Requested Timeout:" + requestedlocktimeout); + locktoken = string.Empty; + lock (ObjectLocks) + { + /* + The table below describes the behavior that occurs when a lock request is made on a resource. + Current State Shared Lock OK Exclusive Lock OK + None True True + Shared Lock True False + Exclusive Lock False False* + + Legend: True = lock may be granted. False = lock MUST NOT be granted. *=It is illegal for a principal to request the same lock twice. + + The current lock state of a resource is given in the leftmost column, and lock requests are listed in the first row. The intersection of a row and column gives the result of a lock request. For example, if a shared lock is held on a resource, and an exclusive lock is requested, the table entry is "false", indicating that the lock must not be granted. + */ + + + //if ObjectLocks doesn't contain the path, then this is a new lock and regardless + //of whether it is Exclusive or Shared it is successful. + if (!ObjectLocks.ContainsKey(path)) + { + ObjectLocks.Add(path, new List()); + + ObjectLocks[path].Add(new WebDaveStoreItemLockInstance(path, lockscope, locktype, lockowner, + ref requestedlocktimeout, ref locktoken, + requestDocument, depth)); + + WebDavServer.Log.Debug("Created New Lock (" + lockscope + "), URI had no locks. Timeout:" + + requestedlocktimeout); + + return (int)HttpStatusCode.OK; + } + + //The fact that ObjectLocks contains this URI means that there is already a lock on this object, + //This means the lock fails because you can only have 1 exclusive lock. + if (lockscope == WebDavLockScope.Exclusive) + { + WebDavServer.Log.Debug("Lock Creation Failed (Exclusive), URI already has a lock."); + return 423; + } + + //If the scope is shared and all other locks on this uri are shared we are ok, otherwise we fail. + if (lockscope == WebDavLockScope.Shared) + if (ObjectLocks[path].Any(itemLock => itemLock.LockScope == WebDavLockScope.Exclusive)) + { + WebDavServer.Log.Debug("Lock Creation Failed (Shared), URI has exclusive lock."); + return 423; + } + //423 (Locked), potentially with 'no-conflicting-lock' precondition code - + //There is already a lock on the resource that is not compatible with the + //requested lock (see lock compatibility table above). + + //If it gets to here, then we are most likely creating another shared lock on the file. + + #region Create New Lock + + ObjectLocks[path].Add(new WebDaveStoreItemLockInstance(path, lockscope, locktype, lockowner, + ref requestedlocktimeout, ref locktoken, + requestDocument, depth)); + + WebDavServer.Log.Debug("Created New Lock (" + lockscope + "), URI had no locks. Timeout:" + + requestedlocktimeout); + + #endregion + + return (int)HttpStatusCode.OK; + } + } + + /// + /// Unlocks the URI passed if the token matches a lock token in use. + /// + /// URI to resource + /// Token used to lock. + /// Owner. + /// + public static int UnLock(Uri path, string locktoken, string owner) + { + CleanLocks(path); + if (string.IsNullOrEmpty(locktoken)) + { + WebDavServer.Log.Debug("Unlock failed, No Token!."); + return (int)HttpStatusCode.BadRequest; + } + + lock (ObjectLocks) + { + if (!ObjectLocks.ContainsKey(path)) + { + WebDavServer.Log.Debug("Unlock failed, Lock does not exist!."); + return (int)HttpStatusCode.Conflict; + } + + WebDaveStoreItemLockInstance ilock = ObjectLocks[path].FirstOrDefault(d => d.Token == locktoken && d.Owner == owner); + if (ilock == null) + return (int)HttpStatusCode.Conflict; + //Remove the lock + ObjectLocks[path].Remove(ilock); + + //if there are no locks left of the uri then remove it from ObjectLocks. + if (ObjectLocks[path].Count == 0) + ObjectLocks.Remove(path); + + WebDavServer.Log.Debug("Unlock successful."); + return (int)HttpStatusCode.NoContent; + } + } + + /// + /// Returns all the locks on the path + /// + /// URI to resource + /// + public static List GetLocks(Uri path) + { + return ObjectLocks.ContainsKey(path) ? ObjectLocks[path].ToList() : new List(); + } + } +} \ No newline at end of file diff --git a/Stores/Locks/WebDaveStoreItemLockInstance.cs b/Stores/Locks/WebDaveStoreItemLockInstance.cs new file mode 100644 index 0000000..0f57fca --- /dev/null +++ b/Stores/Locks/WebDaveStoreItemLockInstance.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; + +namespace WebDAVSharp.Server.Stores.Locks +{ + /// + /// Used to store locks on objects + /// + public class WebDaveStoreItemLockInstance + { + /// + /// The Path locked + /// + public Uri Path + { + get; + private set; + } + + + /// + /// Lock Scope + /// + public WebDavLockScope LockScope + { + get; + private set; + } + + /// + /// Lock Type + /// + public WebDavLockType LockType + { + get; + private set; + } + + /// + /// Owner + /// + public string Owner + { + get; + private set; + } + + /// + /// Requested Timeout + /// + public string RequestedTimeout + { + get; + private set; + } + + /// + /// Token Issued + /// + public string Token + { + get; + private set; + } + + /// + /// Request Document + /// + public XmlDocument RequestDocument + { + get; + private set; + } + + /// + /// If null, it's an infinite checkout. + /// + public DateTime? ExpirationDate + { + get; + private set; + } + + /// + /// + /// + public int Depth + { + get; + private set; + } + + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public WebDaveStoreItemLockInstance(Uri path, WebDavLockScope lockscope, WebDavLockType locktype, string owner, ref string requestedlocktimeout, ref string token, XmlDocument requestdocument, int depth) + { + Path = path; + LockScope = lockscope; + LockType = locktype; + Owner = owner; + Token = token; + RequestDocument = requestdocument; + Token = token = "urn:uuid:" + Guid.NewGuid(); + Depth = depth; + RefreshLock(ref requestedlocktimeout); + } + + /// + /// Refreshes a lock + /// + /// + public void RefreshLock(ref string requestedlocktimeout) + { + if (requestedlocktimeout.Contains("Infinite") && WebDavStoreItemLock.AllowInfiniteCheckouts) + { + requestedlocktimeout = "Infinite"; + ExpirationDate = null; + return; + } + string seconds = requestedlocktimeout.Substring(requestedlocktimeout.IndexOf("Second-", System.StringComparison.Ordinal) + 7); + long lseconds; + if (long.TryParse(seconds, out lseconds)) + { + if (lseconds > WebDavStoreItemLock.MaxCheckOutSeconds) + lseconds = WebDavStoreItemLock.MaxCheckOutSeconds; + RequestedTimeout = requestedlocktimeout = "Second-" + lseconds; + + //Due to latency, if the seconds granted is less than max seconds - 60 then we give them a 1 minute buffer. + ExpirationDate = DateTime.Now.AddSeconds(lseconds < long.MaxValue - 60 ? lseconds + 60 : lseconds); + } + else + { + RequestedTimeout = requestedlocktimeout = "Second-" + WebDavStoreItemLock.MaxCheckOutSeconds; + ExpirationDate = DateTime.Now.AddSeconds(WebDavStoreItemLock.MaxCheckOutSeconds); + } + } + + } +} diff --git a/WebDAVDisposableBase.cs b/WebDAVDisposableBase.cs index b9f3458..a2453a5 100644 --- a/WebDAVDisposableBase.cs +++ b/WebDAVDisposableBase.cs @@ -7,8 +7,13 @@ namespace WebDAVSharp.Server /// public abstract class WebDavDisposableBase : IDisposable { + #region Properties + private bool _isDisposed; + #endregion + + #region Functions /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// @@ -32,10 +37,16 @@ protected void EnsureNotDisposed() throw new ObjectDisposedException(GetType().FullName); } + #endregion + + #region Abstract Functions + /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected abstract void Dispose(bool disposing); + + #endregion } } \ No newline at end of file diff --git a/WebDAVExtensions.cs b/WebDAVExtensions.cs index 90ae1d9..488ec76 100644 --- a/WebDAVExtensions.cs +++ b/WebDAVExtensions.cs @@ -47,13 +47,13 @@ public static Uri GetParentUri(this Uri uri) /// The HTTP status code for the response. /// context /// is null. - public static void SendSimpleResponse(this IHttpListenerContext context, HttpStatusCode statusCode = HttpStatusCode.OK) + public static void SendSimpleResponse(this IHttpListenerContext context, int statusCode = (int)HttpStatusCode.OK) { if (context == null) throw new ArgumentNullException("context"); - context.Response.StatusCode = (int)statusCode; - context.Response.StatusDescription = HttpWorkerRequest.GetStatusDescription((int)statusCode); + context.Response.StatusCode = statusCode; + context.Response.StatusDescription = HttpWorkerRequest.GetStatusDescription(statusCode); context.Response.Close(); } @@ -71,12 +71,14 @@ public static void SendSimpleResponse(this IHttpListenerContext context, HttpSta /// specifies a that is not known to the . public static Uri GetPrefixUri(this Uri uri, WebDavServer server) { - string url = uri.ToString(); - foreach ( - string prefix in - server.Listener.Prefixes.Where( - prefix => url.StartsWith(uri.ToString(), StringComparison.OrdinalIgnoreCase))) - return new Uri(prefix); + + string url = uri.ToString(); + foreach ( + string prefix in + server.Listener.Prefixes.Where( + prefix => url.StartsWith(uri.ToString(), StringComparison.OrdinalIgnoreCase))) + return new Uri(prefix); + throw new WebDavInternalServerException("Unable to find correct server root"); } diff --git a/WebDAVProperty.cs b/WebDAVProperty.cs index 251ea5e..0090237 100644 --- a/WebDAVProperty.cs +++ b/WebDAVProperty.cs @@ -7,6 +7,9 @@ namespace WebDAVSharp.Server /// internal class WebDavProperty { + + #region Variables + /// /// This class implements the core WebDAV server. /// @@ -22,6 +25,10 @@ internal class WebDavProperty /// public string Value; + #endregion + + #region Constructor + /// /// Standard constructor /// @@ -68,6 +75,10 @@ public WebDavProperty(string name, string value, string ns) Namespace = ns; } + #endregion + + #region Functions + /// /// This class implements the core WebDAV server. /// @@ -146,5 +157,29 @@ public XmlElement ToXmlElement(XmlDocument doc) return element; // else, return XmlElement without namespace } + + /// + /// reates an XmlElement from the current WebDAVProperty + /// + /// The XmlDocument where a XmlElement is needed + /// + /// The XmlElement of the current WebDAVProperty object + /// + public XmlElement XmlToXmlElement(XmlDocument doc) + { + // if the DocumentElement is not null, return the XmlElement with namespace + if (doc.DocumentElement == null) + return doc.CreateElement(Name); + // Get the prefix of the namespace + string prefix = doc.DocumentElement.GetPrefixOfNamespace(Namespace); + + // Create the element + XmlElement element = doc.CreateElement(prefix, Name, Namespace); + element.InnerXml = Value; + return element; + // else, return XmlElement without namespace + } + #endregion + } } \ No newline at end of file diff --git a/WebDAVServer.cs b/WebDAVServer.cs index ed1d74b..f0d7347 100644 --- a/WebDAVServer.cs +++ b/WebDAVServer.cs @@ -3,13 +3,16 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Security.Principal; using System.Text; using System.Threading; using Common.Logging; using WebDAVSharp.Server.Adapters; +using WebDAVSharp.Server.Adapters.AuthenticationTypes; using WebDAVSharp.Server.Exceptions; using WebDAVSharp.Server.MethodHandlers; using WebDAVSharp.Server.Stores; +using WebDAVSharp.Server.Stores.Locks; namespace WebDAVSharp.Server { @@ -18,6 +21,8 @@ namespace WebDAVSharp.Server /// public class WebDavServer : WebDavDisposableBase { + + #region Variables /// /// The HTTP user /// @@ -27,13 +32,138 @@ public class WebDavServer : WebDavDisposableBase private readonly bool _ownsListener; private readonly IWebDavStore _store; private readonly Dictionary _methodHandlers; - private readonly ILog _log; + internal readonly static ILog _log = LogManager.GetCurrentClassLogger(); private readonly object _threadLock = new object(); - private ManualResetEvent _stopEvent; private Thread _thread; + #endregion + + #region Properties + + /// + /// Allow users to have Indefinite Locks + /// + public bool AllowInfiniteCheckouts + { + get + { + return WebDavStoreItemLock.AllowInfiniteCheckouts; + } + set + { + WebDavStoreItemLock.AllowInfiniteCheckouts = value; + } + } + + /// + /// The maximum number of seconds a person can check an item out for. + /// + public long MaxCheckOutSeconds + { + get + { + return WebDavStoreItemLock.MaxCheckOutSeconds; + } + set + { + WebDavStoreItemLock.MaxCheckOutSeconds = value; + } + } + /// + /// Logging Interface + /// + public static ILog Log + { + get + { + return _log; + } + } + + /// + /// Gets the this is hosting. + /// + /// + /// The store. + /// + public IWebDavStore Store + { + get + { + return _store; + } + } + + /// + /// Gets the + /// that this + /// uses for + /// the web server portion. + /// + /// + /// The listener. + /// + internal IHttpListener Listener + { + get + { + return _listener; + } + } + + #endregion + + #region Constructor + + /// + /// + /// + /// + /// + /// + public WebDavServer(IWebDavStore store, AuthType authtype, IEnumerable methodHandlers = null) + { + _ownsListener = true; + switch (authtype) + { + case AuthType.Basic: + _listener = new HttpListenerBasicAdapter(); + break; + case AuthType.Negotiate: + _listener = new HttpListenerNegotiateAdapter(); + break; + case AuthType.Anonymous: + _listener = new HttpListenerAnyonymousAdapter(); + break; + + } + methodHandlers = methodHandlers ?? WebDavMethodHandlers.BuiltIn; + + IWebDavMethodHandler[] webDavMethodHandlers = methodHandlers as IWebDavMethodHandler[] ?? methodHandlers.ToArray(); + + if (!webDavMethodHandlers.Any()) + throw new ArgumentException("The methodHandlers collection is empty", "methodHandlers"); + if (webDavMethodHandlers.Any(methodHandler => methodHandler == null)) + throw new ArgumentException("The methodHandlers collection contains a null-reference", "methodHandlers"); + + //_negotiateListener = listener; + _store = store; + var handlersWithNames = + from methodHandler in webDavMethodHandlers + from name in methodHandler.Names + select new + { + name, + methodHandler + }; + _methodHandlers = handlersWithNames.ToDictionary(v => v.name, v => v.methodHandler); + + } + /// + /// This constructor uses a Negotiate Listener if one isn't passed. + /// /// Initializes a new instance of the class. /// /// The @@ -64,7 +194,7 @@ public WebDavServer(IWebDavStore store, IHttpListener listener = null, IEnumerab throw new ArgumentNullException("store"); if (listener == null) { - listener = new HttpListenerAdapter(); + listener = new HttpListenerNegotiateAdapter(); _ownsListener = true; } methodHandlers = methodHandlers ?? WebDavMethodHandlers.BuiltIn; @@ -87,39 +217,11 @@ from name in methodHandler.Names methodHandler }; _methodHandlers = handlersWithNames.ToDictionary(v => v.name, v => v.methodHandler); - _log = LogManager.GetCurrentClassLogger(); - } - - /// - /// Gets the - /// that this - /// uses for - /// the web server portion. - /// - /// - /// The listener. - /// - internal IHttpListener Listener - { - get - { - return _listener; - } } - /// - /// Gets the this is hosting. - /// - /// - /// The store. - /// - public IWebDavStore Store - { - get - { - return _store; - } - } + #endregion + + #region Functions /// /// Releases unmanaged and - optionally - managed resources @@ -145,17 +247,17 @@ protected override void Dispose(bool disposing) /// This WebDAVServer instance is already running, call to Start is invalid at this point /// This instance has been disposed of. /// The server is already running. - public void Start(String Url) - { - Listener.Prefixes.Add(Url); + public void Start(String url) + { + Listener.Prefixes.Add(url); EnsureNotDisposed(); lock (_threadLock) - { + { if (_thread != null) - { + { throw new InvalidOperationException( "This WebDAVServer instance is already running, call to Start is invalid at this point"); - } + } _stopEvent = new ManualResetEvent(false); @@ -182,10 +284,10 @@ public void Stop() lock (_threadLock) { if (_thread == null) - { + { throw new InvalidOperationException( "This WebDAVServer instance is not running, call to Stop is invalid at this point"); - } + } _stopEvent.Set(); _thread.Join(); @@ -238,11 +340,9 @@ private void BackgroundThreadMethod() /// If the server had an internal problem private void ProcessRequest(object state) { - IHttpListenerContext context = (IHttpListenerContext)state; - // For authentication - Thread.SetData(Thread.GetNamedDataSlot(HttpUser), context.AdaptedInstance.User.Identity); + Thread.SetData(Thread.GetNamedDataSlot(HttpUser), Listener.GetIdentity(context)); _log.Info(context.Request.HttpMethod + " " + context.Request.RemoteEndPoint + ": " + context.Request.Url); try @@ -299,7 +399,9 @@ private void ProcessRequest(object state) context.Response.ContentEncoding = Encoding.UTF8; context.Response.ContentLength64 = buffer.Length; context.Response.OutputStream.Write(buffer, 0, buffer.Length); + context.Response.OutputStream.Flush(); } + context.Response.Close(); } finally @@ -307,5 +409,7 @@ private void ProcessRequest(object state) _log.Info(context.Response.StatusCode + " " + context.Response.StatusDescription + ": " + context.Request.HttpMethod + " " + context.Request.RemoteEndPoint + ": " + context.Request.Url); } } + + #endregion } } \ No newline at end of file diff --git a/WebDAVSharp.Server.csproj b/WebDAVSharp.Server.csproj index db72d54..c0018b9 100644 --- a/WebDAVSharp.Server.csproj +++ b/WebDAVSharp.Server.csproj @@ -74,6 +74,8 @@ + + @@ -105,6 +107,10 @@ + + + + @@ -122,11 +128,12 @@ + - + diff --git a/WebDavAuthType.cs b/WebDavAuthType.cs new file mode 100644 index 0000000..822977b --- /dev/null +++ b/WebDavAuthType.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WebDAVSharp.Server +{ + + /// + /// HTTP Authorization Types + /// + public enum AuthType + { + /// + /// Clear Text + /// + Basic, + /// + /// Negotiate + /// + Negotiate, + /// + /// Anonymous + /// + Anonymous + } +}