using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Threading; using Common.Logging; using WebDAVSharp.Server.Adapters; using WebDAVSharp.Server.Exceptions; using WebDAVSharp.Server.MethodHandlers; using WebDAVSharp.Server.Stores; using System.Web; using System.Diagnostics; namespace WebDAVSharp.Server { /// /// This class implements the core WebDAV server. /// public class WebDavServer : WebDavDisposableBase { /// /// The HTTP user /// public const string HttpUser = "HTTP.User"; private readonly IHttpListener _listener; private readonly bool _ownsListener; private readonly IWebDavStore _store; private readonly Dictionary _methodHandlers; private readonly ILog _log; private readonly object _threadLock = new object(); private ManualResetEvent _stopEvent; private Thread _thread; /// /// Initializes a new instance of the class. /// /// The /// store object that will provide /// collections and documents for this /// . /// The /// object that will handle the web server portion of /// the WebDAV server; or /// null to use a fresh one. /// A collection of HTTP method handlers to use by this /// ; /// or /// null to use the built-in method handlers. /// /// is null. /// - or - /// /// is null. /// /// is empty. /// - or - /// /// contains a null-reference. public WebDavServer(IWebDavStore store, IHttpListener listener = null, IEnumerable methodHandlers = null) { if (store == null) throw new ArgumentNullException("store"); if (listener == null) { listener = new HttpListenerAdapter(); _ownsListener = true; } 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"); _listener = 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); _log = LogManager.GetLogger(); } /// /// 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; } } public string ServiceUrl { get; set; } /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { lock (_threadLock) { if (_thread != null) Stop(); } if (_ownsListener) _listener.Dispose(); } ///// ///// Starts this ///// and returns once it has ///// been started successfully. ///// ///// 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); // 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); // _thread = new Thread(BackgroundThreadMethod) // { // Name = "WebDAVServer.Thread", // Priority = ThreadPriority.Highest // }; // _thread.Start(); // } //} /// /// Starts this /// and returns once it has /// been stopped successfully. /// /// This WebDAVServer instance is not running, call to Stop is invalid at this point /// This instance has been disposed of. /// The server is not running. public void Stop() { EnsureNotDisposed(); 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(); _stopEvent.Close(); _stopEvent = null; _thread = null; } } ///// ///// The background thread method. ///// //private void BackgroundThreadMethod() //{ // _log.Info("WebDAVServer background thread has started"); // try // { // _listener.Start(); // while (true) // { // if (_stopEvent.WaitOne(0)) // return; // IHttpListenerContext context = Listener.GetContext(_stopEvent); // if (context == null) // { // _log.Debug("Exiting thread"); // return; // } // ThreadPool.QueueUserWorkItem(ProcessRequest, context); // } // } // finally // { // _listener.Stop(); // _log.Info("WebDAVServer background thread has terminated"); // } //} /// /// Processes the request. /// /// The context. /// If the method to process is not allowed /// If the user is unauthorized or has no access /// If the item was not found /// If a method is not yet implemented /// If the server had an internal problem public void ProcessRequest(HttpContext context, String fileName) { //IHttpListenerContext context = (IHttpListenerContext)state; //TODO: (Charly): enable authentication // For authentication Thread.SetData(Thread.GetNamedDataSlot(HttpUser), context.User?.Identity); _log.Info(context.Request.HttpMethod + " " + fileName); _log.Debug($"WebDAV - {context.Request.HttpMethod} - {fileName} - {context.User?.Identity?.AuthenticationType}: {context.User?.Identity?.Name}"); try { try { string method = context.Request.HttpMethod; IWebDavMethodHandler methodHandler; if (!_methodHandlers.TryGetValue(method, out methodHandler)) throw new WebDavMethodNotAllowedException(string.Format(CultureInfo.InvariantCulture, "%s ({0})", context.Request.HttpMethod)); context.Response.AppendHeader("DAV", "1,2,1#extend"); methodHandler.ProcessRequest(this, context, Store, String.IsNullOrWhiteSpace(fileName) ? "/" : fileName); } catch (WebDavException) { throw; } catch (UnauthorizedAccessException) { throw new WebDavUnauthorizedException(); } catch (FileNotFoundException ex) { _log.Warn(ex.Message); throw new WebDavNotFoundException(innerException: ex); } catch (DirectoryNotFoundException ex) { _log.Warn(ex.Message); throw new WebDavNotFoundException(innerException: ex); } catch (NotImplementedException ex) { _log.Warn(ex.Message); throw new WebDavNotImplementedException(innerException: ex); } catch (Exception ex) { _log.Warn(ex.Message); throw new WebDavInternalServerException(innerException: ex); } } catch (WebDavException ex) { _log.Warn(ex.StatusCode + " " + ex.Message); Trace.WriteLine($"WebDAV - {ex.StatusCode} - {ex.StatusDescription} : {ex.Message}", "Warning"); // context.Response.StatusCode = ex.StatusCode; context.Response.StatusDescription = ex.StatusDescription; if (ex.Message != context.Response.StatusDescription) { byte[] buffer = Encoding.UTF8.GetBytes(ex.Message); context.Response.ContentEncoding = Encoding.UTF8; //context.Response.ContentLength64 = buffer.Length; context.Response.OutputStream.Write(buffer, 0, buffer.Length); } //context.Response.Close(); } finally { _log.Info(context.Response.StatusCode + " " + context.Response.StatusDescription + ": " + context.Request.HttpMethod + " " + fileName); string headers = ""; try { foreach (var key in context.Response.Headers.AllKeys) { headers += $"\t{key}: {context.Response.Headers[key]}\n"; } } catch (Exception ex) { Trace.TraceError(ex.ToString()); } Trace.WriteLine($"WebDAV - {context.Response.StatusCode} {context.Response.StatusDescription}: {context.Request.HttpMethod} {fileName}\n{headers}", "Info"); } } } }