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);
Trace.WriteLine($"\nWebDAV - {context.Request.HttpMethod} - {fileName} - {context.User.Identity.AuthenticationType}: {context.User.Identity.Name}", "Info"); //
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");
}
}
}
}