using System; using System.Collections.Generic; using System.Linq; using System.Text; using HttpServer.HttpModules; using System.IO; using Duplicati.Server.Serialization; namespace Duplicati.Server.WebServer { public class Server { /// /// Option for changing the webroot folder /// public const string OPTION_WEBROOT = "webservice-webroot"; /// /// Option for changing the webservice listen port /// public const string OPTION_PORT = "webservice-port"; /// /// Option for changing the webservice listen interface /// public const string OPTION_INTERFACE = "webservice-interface"; /// /// The default path to the web root /// public const string DEFAULT_OPTION_WEBROOT = "webroot"; /// /// The default listening port /// public const int DEFAULT_OPTION_PORT = 8200; /// /// The default listening interface /// public const string DEFAULT_OPTION_INTERFACE = "loopback"; /// /// The single webserver instance /// private HttpServer.HttpServer m_server; /// /// The webserver listening port /// public readonly int Port; /// /// A string that is sent out instead of password values /// public const string PASSWORD_PLACEHOLDER = "**********"; /// /// Sets up the webserver and starts it /// /// A set of options public Server(IDictionary options) { int port; string portstring; IEnumerable ports = null; options.TryGetValue(OPTION_PORT, out portstring); if (!string.IsNullOrEmpty(portstring)) ports = from n in portstring.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) where int.TryParse(n, out port) select int.Parse(n); if (ports == null || !ports.Any()) ports = new int[] { DEFAULT_OPTION_PORT }; string interfacestring; System.Net.IPAddress listenInterface; options.TryGetValue(OPTION_INTERFACE, out interfacestring); if (string.IsNullOrWhiteSpace(interfacestring)) interfacestring = DEFAULT_OPTION_INTERFACE; if (interfacestring.Trim() == "*" || interfacestring.Trim().Equals("any", StringComparison.InvariantCultureIgnoreCase)) listenInterface = System.Net.IPAddress.Any; else if (interfacestring.Trim() == "loopback") listenInterface = System.Net.IPAddress.Loopback; else listenInterface = System.Net.IPAddress.Parse(interfacestring); // If we are in hosted mode with no specified port, // then try different ports foreach(var p in ports) try { // Due to the way the server is initialized, // we cannot try to start it again on another port, // so we create a new server for each attempt var server = CreateServer(options); server.Start(listenInterface, p); m_server = server; m_server.ServerName = "Duplicati v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); this.Port = p; return; } catch (System.Net.Sockets.SocketException) { } throw new Exception("Unable to open a socket for listening, tried ports: " + string.Join(",", from n in ports select n.ToString())); } private static HttpServer.HttpServer CreateServer(IDictionary options) { HttpServer.HttpServer server = new HttpServer.HttpServer(); server.Add(new AuthenticationHandler()); server.Add(new ControlHandler()); string webroot = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); #if DEBUG if (!System.IO.Directory.Exists(System.IO.Path.Combine(webroot, "webroot"))) { //For debug we go "../../../.." to get out of "GUI/Duplicati.GUI.TrayIcon/bin/debug" string tmpwebroot = System.IO.Path.GetFullPath(System.IO.Path.Combine(webroot, "..", "..", "..", "..")); tmpwebroot = System.IO.Path.Combine(tmpwebroot, "Server"); if (System.IO.Directory.Exists(System.IO.Path.Combine(tmpwebroot, "webroot"))) webroot = tmpwebroot; else { //If we are running the server standalone, we only need to exit "bin/Debug" tmpwebroot = System.IO.Path.GetFullPath(System.IO.Path.Combine(webroot, "..", "..")); if (System.IO.Directory.Exists(System.IO.Path.Combine(tmpwebroot, "webroot"))) webroot = tmpwebroot; } } #endif webroot = System.IO.Path.Combine(webroot, "webroot"); if (options.ContainsKey(OPTION_WEBROOT)) { string userroot = options[OPTION_WEBROOT]; #if DEBUG //In debug mode we do not care where the path points #else //In release mode we check that the user supplied path is located // in the same folders as the running application, to avoid users // that inadvertently expose top level folders if (!string.IsNullOrWhiteSpace(userroot) && ( userroot.StartsWith(Library.Utility.Utility.AppendDirSeparator(System.Reflection.Assembly.GetExecutingAssembly().Location), Library.Utility.Utility.ClientFilenameStringComparision) || userroot.StartsWith(Library.Utility.Utility.AppendDirSeparator(Program.StartupPath), Library.Utility.Utility.ClientFilenameStringComparision) ) ) #endif { webroot = userroot; } } FileModule fh = new FileModule("/", webroot); fh.AddDefaultMimeTypes(); fh.MimeTypes.Add("htc", "text/x-component"); fh.MimeTypes.Add("json", "application/json"); fh.MimeTypes.Add("map", "application/json"); server.Add(fh); server.Add(new IndexHtmlHandler(System.IO.Path.Combine(webroot, "index.html"))); #if DEBUG //For debugging, it is nice to know when we get a 404 server.Add(new DebugReportHandler()); #endif return server; } private class DebugReportHandler : HttpModule { public override bool Process(HttpServer.IHttpRequest request, HttpServer.IHttpResponse response, HttpServer.Sessions.IHttpSession session) { System.Diagnostics.Trace.WriteLine(string.Format("Rejecting request for {0}", request.Uri)); return false; } } } }