302 lines
11 KiB
C#
302 lines
11 KiB
C#
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 {
|
|
/// <summary>
|
|
/// This class implements the core WebDAV server.
|
|
/// </summary>
|
|
public class WebDavServer : WebDavDisposableBase {
|
|
/// <summary>
|
|
/// The HTTP user
|
|
/// </summary>
|
|
public const string HttpUser = "HTTP.User";
|
|
|
|
private readonly IHttpListener _listener;
|
|
private readonly bool _ownsListener;
|
|
private readonly IWebDavStore _store;
|
|
private readonly Dictionary<string, IWebDavMethodHandler> _methodHandlers;
|
|
private readonly ILog _log;
|
|
private readonly object _threadLock = new object();
|
|
|
|
private ManualResetEvent _stopEvent;
|
|
private Thread _thread;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="WebDavServer" /> class.
|
|
/// </summary>
|
|
/// <param name="store">The
|
|
/// <see cref="IWebDavStore" /> store object that will provide
|
|
/// collections and documents for this
|
|
/// <see cref="WebDavServer" />.</param>
|
|
/// <param name="listener">The
|
|
/// <see cref="IHttpListener" /> object that will handle the web server portion of
|
|
/// the WebDAV server; or
|
|
/// <c>null</c> to use a fresh one.</param>
|
|
/// <param name="methodHandlers">A collection of HTTP method handlers to use by this
|
|
/// <see cref="WebDavServer" />;
|
|
/// or
|
|
/// <c>null</c> to use the built-in method handlers.</param>
|
|
/// <exception cref="System.ArgumentNullException"><para>
|
|
/// <paramref name="listener" /> is <c>null</c>.</para>
|
|
/// <para>- or -</para>
|
|
/// <para>
|
|
/// <paramref name="store" /> is <c>null</c>.</para></exception>
|
|
/// <exception cref="System.ArgumentException"><para>
|
|
/// <paramref name="methodHandlers" /> is empty.</para>
|
|
/// <para>- or -</para>
|
|
/// <para>
|
|
/// <paramref name="methodHandlers" /> contains a <c>null</c>-reference.</para></exception>
|
|
public WebDavServer(IWebDavStore store, IHttpListener listener = null, IEnumerable<IWebDavMethodHandler> 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<WebDavServer>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the
|
|
/// <see cref="IHttpListener" /> that this
|
|
/// <see cref="WebDavServer" /> uses for
|
|
/// the web server portion.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The listener.
|
|
/// </value>
|
|
internal IHttpListener Listener {
|
|
get {
|
|
return _listener;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the <see cref="IWebDavStore" /> this <see cref="WebDavServer" /> is hosting.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The store.
|
|
/// </value>
|
|
public IWebDavStore Store {
|
|
get {
|
|
return _store;
|
|
}
|
|
}
|
|
|
|
public string ServiceUrl { get; set; }
|
|
|
|
/// <summary>
|
|
/// Releases unmanaged and - optionally - managed resources
|
|
/// </summary>
|
|
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
|
protected override void Dispose(bool disposing) {
|
|
lock (_threadLock) {
|
|
if (_thread != null)
|
|
Stop();
|
|
}
|
|
|
|
if (_ownsListener)
|
|
_listener.Dispose();
|
|
}
|
|
|
|
///// <summary>
|
|
///// Starts this
|
|
///// <see cref="WebDavServer" /> and returns once it has
|
|
///// been started successfully.
|
|
///// </summary>
|
|
///// <exception cref="System.InvalidOperationException">This WebDAVServer instance is already running, call to Start is invalid at this point</exception>
|
|
///// <exception cref="ObjectDisposedException">This <see cref="WebDavServer" /> instance has been disposed of.</exception>
|
|
///// <exception cref="InvalidOperationException">The server is already running.</exception>
|
|
//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();
|
|
// }
|
|
//}
|
|
|
|
/// <summary>
|
|
/// Starts this
|
|
/// <see cref="WebDavServer" /> and returns once it has
|
|
/// been stopped successfully.
|
|
/// </summary>
|
|
/// <exception cref="System.InvalidOperationException">This WebDAVServer instance is not running, call to Stop is invalid at this point</exception>
|
|
/// <exception cref="ObjectDisposedException">This <see cref="WebDavServer" /> instance has been disposed of.</exception>
|
|
/// <exception cref="InvalidOperationException">The server is not running.</exception>
|
|
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;
|
|
}
|
|
}
|
|
|
|
///// <summary>
|
|
///// The background thread method.
|
|
///// </summary>
|
|
//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");
|
|
// }
|
|
//}
|
|
|
|
/// <summary>
|
|
/// Processes the request.
|
|
/// </summary>
|
|
/// <param name="context">The context.</param>
|
|
/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavMethodNotAllowedException">If the method to process is not allowed</exception>
|
|
/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavUnauthorizedException">If the user is unauthorized or has no access</exception>
|
|
/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavNotFoundException">If the item was not found</exception>
|
|
/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavNotImplementedException">If a method is not yet implemented</exception>
|
|
/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavInternalServerException">If the server had an internal problem</exception>
|
|
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");
|
|
}
|
|
}
|
|
}
|
|
} |