[Pkg-mono-svn-commits] [xsp] 04/10: Imported Upstream version 4.2
Jo Shields
directhex at moszumanska.debian.org
Mon Nov 30 15:25:19 UTC 2015
This is an automated email from the git hooks/post-receive script.
directhex pushed a commit to annotated tag debian/4.2-1
in repository xsp.
commit 1b3828afa30e4e5ab25f67293f7dbe64819ab055
Author: Jo Shields <directhex at apebox.org>
Date: Mon Nov 30 14:23:23 2015 +0000
Imported Upstream version 4.2
---
configure | 20 +-
configure.ac | 2 +-
src/Mono.WebServer.XSP/Makefile.am | 2 +-
src/Mono.WebServer.XSP/Makefile.in | 2 +-
src/Mono.WebServer.XSP/Mono.WebServer.XSP.sources | 10 +
src/Mono.WebServer/ApplicationServer.cs | 551 ++++++++++++
src/Mono.WebServer/BaseApplicationHost.cs | 273 ++++++
src/Mono.WebServer/BaseRequestBroker.cs | 268 ++++++
src/Mono.WebServer/EndOfRequestHandler.cs | 39 +
src/Mono.WebServer/FinePlatformID.cs | 35 +
src/Mono.WebServer/HttpErrors.cs | 80 ++
src/Mono.WebServer/IApplicationHost.cs | 48 +
src/Mono.WebServer/IRequestBroker.cs | 38 +
src/Mono.WebServer/IdentityToken.cs | 51 ++
src/Mono.WebServer/InitialWorkerRequest.cs | 281 ++++++
src/Mono.WebServer/LingeringNetworkStream.cs | 100 +++
src/Mono.WebServer/LockRecursionException.cs | 54 ++
src/Mono.WebServer/LockRecursionPolicy.cs | 36 +
src/Mono.WebServer/Log/FileLogger.cs | 86 ++
src/Mono.WebServer/Log/ILogger.cs | 35 +
src/Mono.WebServer/Log/LogLevel.cs | 43 +
src/Mono.WebServer/Log/Logger.cs | 151 ++++
src/Mono.WebServer/Makefile.am | 2 +-
src/Mono.WebServer/Makefile.in | 2 +-
src/Mono.WebServer/MapPathEventArgs.cs | 67 ++
src/Mono.WebServer/MapPathEventHandler.cs | 39 +
src/Mono.WebServer/Mono.WebServer.sources | 53 ++
src/Mono.WebServer/MonoWorkerRequest.cs | 626 +++++++++++++
.../Options/ConfigurationManager.Fields.cs | 137 +++
src/Mono.WebServer/Options/ConfigurationManager.cs | 144 +++
.../Options/ConfigurationManagerExtensions.cs | 73 ++
src/Mono.WebServer/Options/Descriptions.cs | 93 ++
.../Options/IHelpConfigurationManager.cs | 37 +
src/Mono.WebServer/Options/Options.cs | 997 +++++++++++++++++++++
.../Options/ServerConfigurationManager.cs | 78 ++
src/Mono.WebServer/Options/Settings/BoolSetting.cs | 39 +
src/Mono.WebServer/Options/Settings/EnumSetting.cs | 50 ++
src/Mono.WebServer/Options/Settings/ISetting.cs | 44 +
.../Options/Settings/Int32Setting.cs | 39 +
.../Options/Settings/NullableInt32Setting.cs | 39 +
.../Options/Settings/NullableSetting.cs | 51 ++
.../Options/Settings/NullableUInt16Setting.cs | 39 +
src/Mono.WebServer/Options/Settings/Parser.cs | 31 +
src/Mono.WebServer/Options/Settings/Setting.cs | 95 ++
.../Options/Settings/SettingSource.cs | 38 +
.../Options/Settings/SettingsCollection.cs | 38 +
.../Options/Settings/StringSetting.cs | 45 +
.../Options/Settings/UInt16Setting.cs | 39 +
.../Options/Settings/UInt32Setting.cs | 39 +
src/Mono.WebServer/Paths.cs | 85 ++
src/Mono.WebServer/Platform.cs | 143 +++
src/Mono.WebServer/RequestData.cs | 61 ++
src/Mono.WebServer/RequestLineException.cs | 38 +
src/Mono.WebServer/SearchPattern.cs | 195 ++++
src/Mono.WebServer/UnregisterRequestEventArgs.cs | 44 +
.../UnregisterRequestEventHandler.cs | 33 +
src/Mono.WebServer/VPathToHost.cs | 153 ++++
src/Mono.WebServer/Version.cs | 62 ++
src/Mono.WebServer/WebSource.cs | 55 ++
src/Mono.WebServer/WebTrace.cs | 137 +++
src/Mono.WebServer/Worker.cs | 62 ++
61 files changed, 6232 insertions(+), 15 deletions(-)
diff --git a/configure b/configure
index db691a6..33a7413 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for xsp 4.0.
+# Generated by GNU Autoconf 2.69 for xsp 4.2.
#
# Report bugs to <http://bugzilla.xamarin.com/>.
#
@@ -590,8 +590,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='xsp'
PACKAGE_TARNAME='xsp'
-PACKAGE_VERSION='4.0'
-PACKAGE_STRING='xsp 4.0'
+PACKAGE_VERSION='4.2'
+PACKAGE_STRING='xsp 4.2'
PACKAGE_BUGREPORT='http://bugzilla.xamarin.com/'
PACKAGE_URL=''
@@ -1373,7 +1373,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures xsp 4.0 to adapt to many kinds of systems.
+\`configure' configures xsp 4.2 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1444,7 +1444,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of xsp 4.0:";;
+ short | recursive ) echo "Configuration of xsp 4.2:";;
esac
cat <<\_ACEOF
@@ -1566,7 +1566,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-xsp configure 4.0
+xsp configure 4.2
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
@@ -1844,7 +1844,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by xsp $as_me 4.0, which was
+It was created by xsp $as_me 4.2, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
@@ -2819,7 +2819,7 @@ fi
# Define the identity of the package.
PACKAGE='xsp'
- VERSION='4.0'
+ VERSION='4.2'
cat >>confdefs.h <<_ACEOF
@@ -12967,7 +12967,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by xsp $as_me 4.0, which was
+This file was extended by xsp $as_me 4.2, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -13024,7 +13024,7 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
-xsp config.status 4.0
+xsp config.status 4.2
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
diff --git a/configure.ac b/configure.ac
index ad9be55..29b990f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.53])
-AC_INIT([xsp], [4.0], [http://bugzilla.xamarin.com/])
+AC_INIT([xsp], [4.2], [http://bugzilla.xamarin.com/])
AC_CANONICAL_SYSTEM
AC_CONFIG_MACRO_DIR([build/m4])
AM_INIT_AUTOMAKE([foreign])
diff --git a/src/Mono.WebServer.XSP/Makefile.am b/src/Mono.WebServer.XSP/Makefile.am
index 1584803..2514380 100644
--- a/src/Mono.WebServer.XSP/Makefile.am
+++ b/src/Mono.WebServer.XSP/Makefile.am
@@ -20,7 +20,7 @@ pkgconfig_DATA += xsp-4.pc
sources = $(shell cat $(srcdir)/Mono.WebServer.XSP.sources)
build_sources = $(addprefix $(srcdir)/, $(sources)) AssemblyInfo.cs
-EXTRA_DIST = $(sources) AssemblyInfo.cs.in
+EXTRA_DIST = $(sources) Mono.WebServer.XSP.sources AssemblyInfo.cs.in
xsp4.exe: $(build_sources)
$(DMCS) -d:NET_2_0 -d:NET_4_0 $(MCSFLAGS) $(references4) /out:$@ $(build_sources)
diff --git a/src/Mono.WebServer.XSP/Makefile.in b/src/Mono.WebServer.XSP/Makefile.in
index a5316df..d2702c5 100644
--- a/src/Mono.WebServer.XSP/Makefile.in
+++ b/src/Mono.WebServer.XSP/Makefile.in
@@ -320,7 +320,7 @@ references4 = $(references_common) -r:../Mono.WebServer/4.0/Mono.WebServer2.dll
pkgconfig_DATA = xsp-2.pc xsp-4.pc
sources = $(shell cat $(srcdir)/Mono.WebServer.XSP.sources)
build_sources = $(addprefix $(srcdir)/, $(sources)) AssemblyInfo.cs
-EXTRA_DIST = $(sources) AssemblyInfo.cs.in
+EXTRA_DIST = $(sources) Mono.WebServer.XSP.sources AssemblyInfo.cs.in
all: all-am
.SUFFIXES:
diff --git a/src/Mono.WebServer.XSP/Mono.WebServer.XSP.sources b/src/Mono.WebServer.XSP/Mono.WebServer.XSP.sources
new file mode 100644
index 0000000..855ad43
--- /dev/null
+++ b/src/Mono.WebServer.XSP/Mono.WebServer.XSP.sources
@@ -0,0 +1,10 @@
+./CompatTuple.cs
+./ConfigurationManager.cs
+./main.cs
+./SecurityConfiguration.cs
+./SslInformation.cs
+./XSPApplicationHost.cs
+./XSPRequestBroker.cs
+./XSPWebSource.cs
+./XSPWorker.cs
+./XSPWorkerRequest.cs
diff --git a/src/Mono.WebServer/ApplicationServer.cs b/src/Mono.WebServer/ApplicationServer.cs
new file mode 100644
index 0000000..df8e315
--- /dev/null
+++ b/src/Mono.WebServer/ApplicationServer.cs
@@ -0,0 +1,551 @@
+//
+// ApplicationServer.cs
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo at ximian.com)
+// Lluis Sanchez Gual (lluis at ximian.com)
+//
+// Copyright (c) Copyright 2002-2007 Novell, Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using System.Xml;
+using System.Text;
+using System.Threading;
+using System.IO;
+using Mono.WebServer.Log;
+
+namespace Mono.WebServer
+{
+ // ApplicationServer runs the main server thread, which accepts client
+ // connections and forwards the requests to the correct web application.
+ // ApplicationServer takes an WebSource object as parameter in the
+ // constructor. WebSource provides methods for getting some objects
+ // whose behavior is specific to XSP or mod_mono.
+
+ // Each web application lives in its own application domain, and incoming
+ // requests are processed in the corresponding application domain.
+ // Since the client Socket can't be passed from one domain to the other, the
+ // flow of information must go through the cross-app domain channel.
+
+ // For each application two objects are created:
+ // 1) a IApplicationHost object is created in the application domain
+ // 2) a IRequestBroker is created in the main domain.
+ //
+ // The IApplicationHost is used by the ApplicationServer to start the
+ // processing of a request in the application domain.
+ // The IRequestBroker is used from the application domain to access
+ // information in the main domain.
+ //
+ // The complete sequence of servicing a request is the following:
+ //
+ // 1) The listener accepts an incoming connection.
+ // 2) An Worker object is created (through the WebSource), and it is
+ // queued in the thread pool.
+ // 3) When the Worker's run method is called, it registers itself in
+ // the application's request broker, and gets a request id. All this is
+ // done in the main domain.
+ // 4) The Worker starts the request processing by making a cross-app domain
+ // call to the application host. It passes as parameters the request id
+ // and other information already read from the request.
+ // 5) The application host executes the request. When it needs to read or
+ // write request data, it performs remote calls to the request broker,
+ // passing the request id provided by the Worker.
+ // 6) When the request broker receives a call from the application host,
+ // it locates the Worker registered with the provided request id and
+ // forwards the call to it.
+
+ public class ApplicationServer : MarshalByRefObject
+ {
+ readonly WebSource webSource;
+ bool started;
+ bool stop;
+ Socket listen_socket;
+
+ Thread runner;
+
+ // This is much faster than hashtable for typical cases.
+ readonly List<VPathToHost> vpathToHost = new List<VPathToHost> ();
+
+ public bool SingleApplication { get; set; }
+
+ public IApplicationHost AppHost {
+ get { return vpathToHost [0].AppHost; }
+ set { vpathToHost [0].AppHost = value; }
+ }
+
+ public IRequestBroker Broker {
+ get { return vpathToHost [0].RequestBroker; }
+ set { vpathToHost [0].RequestBroker = value; }
+ }
+
+ public int Port {
+ get {
+ if (listen_socket == null || !listen_socket.IsBound)
+ return -1;
+
+ var iep = listen_socket.LocalEndPoint as IPEndPoint;
+ if (iep == null)
+ return -1;
+
+ return iep.Port;
+ }
+ }
+
+ public string PhysicalRoot { get; private set; }
+
+ public bool Verbose { get; set; }
+
+ [Obsolete ("Use the .ctor that takes a 'physicalRoot' argument instead")]
+ public ApplicationServer (WebSource source) : this (source, Environment.CurrentDirectory)
+ {
+ }
+
+ public ApplicationServer (WebSource source, string physicalRoot)
+ {
+ if (source == null)
+ throw new ArgumentNullException ("source");
+ if (String.IsNullOrEmpty (physicalRoot))
+ throw new ArgumentNullException ("physicalRoot");
+
+ webSource = source;
+ PhysicalRoot = physicalRoot;
+ }
+
+ public void AddApplication (string vhost, int vport, string vpath, string fullPath)
+ {
+ char dirSepChar = Path.DirectorySeparatorChar;
+ if (fullPath != null && !fullPath.EndsWith (dirSepChar.ToString ()))
+ fullPath += dirSepChar;
+
+ // TODO: Check for duplicates, sort, optimize, etc.
+ if (Verbose && !SingleApplication) {
+ Logger.Write (LogLevel.Notice, "Registering application:");
+ Logger.Write(LogLevel.Notice, " Host: {0}", vhost ?? "any");
+ Logger.Write(LogLevel.Notice, " Port: {0}", (vport != -1) ? vport.ToString () : "any");
+
+ Logger.Write(LogLevel.Notice, " Virtual path: {0}", vpath);
+ Logger.Write(LogLevel.Notice, " Physical path: {0}", fullPath);
+ }
+
+ vpathToHost.Add (new VPathToHost (vhost, vport, vpath, fullPath));
+ }
+
+ public void AddApplicationsFromConfigDirectory (string directoryName)
+ {
+ if (Verbose && !SingleApplication) {
+ Logger.Write(LogLevel.Notice, "Adding applications from *.webapp files in directory '{0}'", directoryName);
+ }
+
+ var di = new DirectoryInfo (directoryName);
+ if (!di.Exists) {
+ Logger.Write (LogLevel.Error, "Directory {0} does not exist.", directoryName);
+ return;
+ }
+
+ foreach (FileInfo fi in di.GetFiles ("*.webapp"))
+ AddApplicationsFromConfigFile (fi.FullName);
+ }
+
+ public void AddApplicationsFromConfigFile (string fileName)
+ {
+ if (Verbose && !SingleApplication) {
+ Logger.Write(LogLevel.Notice, "Adding applications from config file '{0}'", fileName);
+ }
+
+ try {
+ var doc = new XmlDocument ();
+ doc.Load (fileName);
+
+ foreach (XmlElement el in doc.SelectNodes ("//web-application")) {
+ AddApplicationFromElement (el);
+ }
+ } catch {
+ Logger.Write(LogLevel.Error, "Error loading '{0}'", fileName);
+ throw;
+ }
+ }
+
+ void AddApplicationFromElement (XmlNode el)
+ {
+ XmlNode n = el.SelectSingleNode ("enabled");
+ if (n != null && n.InnerText.Trim () == "false")
+ return;
+
+ string vpath = el.SelectSingleNode ("vpath").InnerText;
+ string path = el.SelectSingleNode ("path").InnerText;
+
+ string vhost = null;
+ n = el.SelectSingleNode ("vhost");
+#if !MOD_MONO_SERVER
+ if (n != null)
+ vhost = n.InnerText;
+#else
+ // TODO: support vhosts in xsp.exe
+ string name = el.SelectSingleNode ("name").InnerText;
+ if (verbose && !single_app)
+ Logger.Write (LogLevel.Warning, ("Ignoring vhost {0} for {1}", n.InnerText, name);
+#endif
+
+ int vport = -1;
+ n = el.SelectSingleNode ("vport");
+#if !MOD_MONO_SERVER
+ if (n != null)
+ vport = Convert.ToInt32 (n.InnerText);
+#else
+ // TODO: Listen on different ports
+ if (verbose && !single_app)
+ Logger.Write (LogLevel.Warning, ("Ignoring vport {0} for {1}", n.InnerText, name);
+#endif
+
+ AddApplication (vhost, vport, vpath, path);
+ }
+
+ public void AddApplicationsFromCommandLine (string applications)
+ {
+ if (applications == null)
+ throw new ArgumentNullException ("applications");
+
+ if (applications.Length == 0)
+ return;
+
+ if (Verbose && !SingleApplication) {
+ Logger.Write(LogLevel.Notice, "Adding applications '{0}'...", applications);
+ }
+
+ string [] apps = applications.Split (',');
+
+ foreach (string str in apps) {
+ string [] app = str.Split (':');
+
+ if (app.Length < 2 || app.Length > 4)
+ throw new ArgumentException ("Should be something like " +
+ "[[hostname:]port:]VPath:realpath");
+
+ int vport;
+ int pos = 0;
+
+ string vhost = app.Length >= 3 ? app[pos++] : null;
+
+ if (app.Length >= 4) {
+ // FIXME: support more than one listen port.
+ vport = Convert.ToInt16 (app[pos++]);
+ } else {
+ vport = -1;
+ }
+
+ string vpath = app [pos++];
+ string realpath = app[pos++];
+
+ if (!vpath.EndsWith ("/"))
+ vpath += "/";
+
+ string fullPath = Path.GetFullPath (realpath);
+ AddApplication (vhost, vport, vpath, fullPath);
+ }
+ }
+
+ [Obsolete]
+ public bool Start (bool bgThread, Exception initialException, int backlog)
+ {
+ return Start (bgThread, backlog);
+ }
+
+ public bool Start (bool bgThread, int backlog)
+ {
+ if (started)
+ throw new InvalidOperationException ("The server is already started.");
+
+ if (vpathToHost == null)
+ throw new InvalidOperationException ("SetApplications must be called first.");
+
+ if (SingleApplication) {
+ var v = vpathToHost [0];
+ v.AppHost = AppHost;
+ // Link the host in the application domain with a request broker in the *same* domain
+ // Not needed for SingleApplication and mod_mono
+ v.RequestBroker = webSource.CreateRequestBroker ();
+ AppHost.RequestBroker = v.RequestBroker;
+ }
+
+ listen_socket = webSource.CreateSocket ();
+ listen_socket.Listen (backlog);
+ runner = new Thread (RunServer) {IsBackground = bgThread};
+ runner.Start ();
+ stop = false;
+ return true;
+ }
+
+ public void Stop ()
+ {
+ if (!started)
+ throw new InvalidOperationException ("The server is not started.");
+
+ if (stop)
+ return; // Just ignore, as we're already stopping
+
+ stop = true;
+ webSource.Dispose ();
+
+ // A foreground thread is required to end cleanly
+ var stopThread = new Thread (RealStop);
+ stopThread.Start ();
+ }
+
+ public void ShutdownSockets ()
+ {
+ if (listen_socket != null) {
+ try {
+ listen_socket.Close ();
+ } catch {
+ } finally {
+ listen_socket = null;
+ }
+ }
+ }
+
+ void RealStop ()
+ {
+ started = false;
+ runner.Abort ();
+ ShutdownSockets ();
+ UnloadAll ();
+ }
+
+ public void UnloadAll ()
+ {
+ lock (vpathToHost) {
+ foreach (VPathToHost v in vpathToHost) {
+ v.UnloadHost ();
+ }
+ }
+ }
+
+ void SetSocketOptions (Socket sock)
+ {
+
+ try {
+ sock.LingerState = new LingerOption (true, 15);
+#if !MOD_MONO_SERVER
+ sock.SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 15000); // 15s
+ sock.SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 15000); // 15s
+#endif
+ } catch {
+ // Ignore exceptions here for systems that do not support these options.
+ }
+ }
+
+ void RunServer ()
+ {
+ started = true;
+ var args = new SocketAsyncEventArgs ();
+ args.Completed += OnAccept;
+ listen_socket.AcceptAsync (args);
+ if (runner.IsBackground)
+ return;
+
+ while (true) // Just sleep until we're aborted.
+ Thread.Sleep (1000000);
+ }
+
+ void OnAccept (object sender, SocketAsyncEventArgs e)
+ {
+ if (!started && SingleApplication) {
+ // We are shutting down. Last call...
+ Environment.Exit (0);
+ }
+
+ Socket accepted = e.AcceptSocket;
+ e.AcceptSocket = null;
+
+ if (e.SocketError != SocketError.Success) {
+ CloseSocket(accepted);
+ accepted = null;
+ }
+
+ try {
+ if (started)
+ listen_socket.AcceptAsync (e);
+ } catch (Exception ex) {
+ if (accepted != null)
+ CloseSocket (accepted);
+
+ // not much we can do. fast fail by killing the process.
+ Logger.Write (LogLevel.Error, "Unable to accept socket. Exiting the process. {0}", ex);
+ Environment.Exit (1);
+ }
+
+ if (accepted == null)
+ return;
+
+ SetSocketOptions (accepted);
+ StartRequest (accepted, 0);
+ }
+
+ void CloseSocket(Socket socket)
+ {
+ if (socket == null)
+ return;
+
+ // attempt a quick RST of the connection
+ try {
+ socket.LingerState = new LingerOption (true, 0);
+ } catch {
+ // ignore
+ }
+
+ try {
+ socket.Close();
+ } catch {
+ // ignore
+ }
+ }
+
+ void SendException (Socket socket, Exception ex)
+ {
+ var sb = new StringBuilder ();
+ string now = DateTime.Now.ToUniversalTime ().ToString ("r");
+
+ sb.Append ("HTTP/1.0 500 Server error\r\n");
+ sb.AppendFormat ("Date: {0}\r\n" +
+ "Expires: {0}\r\n" +
+ "Last-Modified: {0}\r\n", now);
+ sb.AppendFormat ("Expires; {0}\r\n", now);
+ sb.Append ("Cache-Control: private, must-revalidate, max-age=0\r\n");
+ sb.Append ("Content-Type: text/html; charset=UTF-8\r\n");
+ sb.Append ("Connection: close\r\n\r\n");
+
+ sb.AppendFormat ("<html><head><title>Exception: {0}</title></head><body>" +
+ "<h1>Exception caught.</h1>" +
+ "<pre>{0}</pre>" +
+ "</body></html>", ex);
+
+ byte[] data = Encoding.UTF8.GetBytes (sb.ToString ());
+ try {
+ int sent = socket.Send (data);
+ if (sent != data.Length)
+ throw new IOException ("Blocking send did not send entire buffer");
+ } catch (Exception ex2) {
+ Logger.Write(LogLevel.Error, "Failed to send exception:");
+ Logger.Write(ex);
+ Logger.Write(LogLevel.Error, "Exception ocurred while sending:");
+ Logger.Write(ex2);
+ }
+ }
+
+ void StartRequest (Socket accepted, int reuses)
+ {
+ try {
+ // The next line can throw (reusing and the client closed)
+ Worker worker = webSource.CreateWorker (accepted, this);
+ worker.SetReuseCount (reuses);
+ if (worker.IsAsync)
+ worker.Run (null);
+ else
+ ThreadPool.QueueUserWorkItem (worker.Run);
+ } catch (Exception) {
+ try {
+ if (accepted != null) {
+ try {
+ if (accepted.Connected)
+ accepted.Shutdown (SocketShutdown.Both);
+ } catch {
+ // ignore
+ }
+
+ accepted.Close ();
+ }
+ } catch {
+ // ignore
+ }
+ }
+ }
+
+ public void ReuseSocket (Socket sock, int reuses)
+ {
+ StartRequest (sock, reuses);
+ }
+
+ public VPathToHost GetApplicationForPath (string vhost, int port, string path,
+ bool defaultToRoot)
+ {
+ if (SingleApplication)
+ return vpathToHost [0];
+
+ VPathToHost bestMatch = null;
+ int bestMatchLength = 0;
+
+ for (int i = vpathToHost.Count - 1; i >= 0; i--) {
+ var v = vpathToHost [i];
+ int matchLength = v.vpath.Length;
+ if (matchLength <= bestMatchLength || !v.Match (vhost, port, path))
+ continue;
+
+ bestMatchLength = matchLength;
+ bestMatch = v;
+ }
+
+ if (bestMatch != null) {
+ lock (bestMatch) {
+ if (bestMatch.AppHost == null)
+ bestMatch.CreateHost (this, webSource);
+ }
+ return bestMatch;
+ }
+
+ if (defaultToRoot)
+ return GetApplicationForPath (vhost, port, "/", false);
+
+ if (Verbose)
+ Logger.Write(LogLevel.Error, "No application defined for: {0}:{1}{2}", vhost, port, path);
+
+ return null;
+ }
+
+ public VPathToHost GetSingleApp ()
+ {
+ if (vpathToHost.Count == 1)
+ return vpathToHost [0];
+ return null;
+ }
+
+ public void DestroyHost (IApplicationHost host)
+ {
+ // Called when the host appdomain is being unloaded
+ for (int i = vpathToHost.Count - 1; i >= 0; i--) {
+ var v = vpathToHost [i];
+ if (v.TryClearHost (host))
+ break;
+ }
+ }
+
+ public override object InitializeLifetimeService ()
+ {
+ return null;
+ }
+ }
+}
+
diff --git a/src/Mono.WebServer/BaseApplicationHost.cs b/src/Mono.WebServer/BaseApplicationHost.cs
new file mode 100644
index 0000000..21c8b3b
--- /dev/null
+++ b/src/Mono.WebServer/BaseApplicationHost.cs
@@ -0,0 +1,273 @@
+// Mono.WebServer.BaseApplicationHost
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo at ximian.com)
+// Lluis Sanchez Gual (lluis at ximian.com)
+//
+// (C) Copyright 2004 Novell, Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO.Foo;
+using System.Threading;
+using System.Web;
+using System.Web.Configuration;
+using System.Collections.Generic;
+using Mono.WebServer.Log;
+
+namespace Mono.WebServer
+{
+ public class BaseApplicationHost : MarshalByRefObject, IApplicationHost
+ {
+ static readonly ReaderWriterLockSlim handlersCacheLock = new ReaderWriterLockSlim ();
+
+ string path;
+ string vpath;
+ readonly EndOfRequestHandler endOfRequest;
+ Dictionary <string, bool> handlersCache;
+
+ public BaseApplicationHost ()
+ {
+ endOfRequest = EndOfRequest;
+ AppDomain.CurrentDomain.DomainUnload += OnUnload;
+ }
+
+ public void Unload ()
+ {
+ HttpRuntime.UnloadAppDomain ();
+ }
+
+ public void OnUnload (object o, EventArgs args)
+ {
+ if (Server != null)
+ Server.DestroyHost (this);
+ }
+
+ public override object InitializeLifetimeService ()
+ {
+ return null; // who wants to live forever?
+ }
+
+ public ApplicationServer Server { get; set; }
+
+ public string Path {
+ get {
+ if (path == null)
+ path = AppDomain.CurrentDomain.GetData (".appPath").ToString ();
+
+ return path;
+ }
+ }
+
+ public string VPath {
+ get {
+ if (vpath == null)
+ vpath = AppDomain.CurrentDomain.GetData (".appVPath").ToString ();
+
+ return vpath;
+ }
+ }
+
+ public AppDomain Domain {
+ get { return AppDomain.CurrentDomain; }
+ }
+
+ public IRequestBroker RequestBroker { get; set; }
+
+ protected void ProcessRequest (MonoWorkerRequest mwr)
+ {
+ if (mwr == null)
+ throw new ArgumentNullException ("mwr");
+
+ if (!mwr.ReadRequestData ()) {
+ EndOfRequest (mwr);
+ return;
+ }
+
+ mwr.EndOfRequestEvent += endOfRequest;
+ try {
+ mwr.ProcessRequest ();
+ } catch (ThreadAbortException) {
+ Thread.ResetAbort ();
+ } catch (Exception ex) { // should "never" happen
+ // we don't know what the request state is,
+ // better write the exception to the console
+ // than forget it.
+ Logger.Write(LogLevel.Error, "Unhandled exception: {0}", ex);
+ EndOfRequest (mwr);
+ }
+ }
+
+ public void EndOfRequest (MonoWorkerRequest mwr)
+ {
+ try {
+ mwr.CloseConnection ();
+ } catch {
+ } finally {
+ var brb = RequestBroker as BaseRequestBroker;
+ if (brb != null)
+ brb.UnregisterRequest (mwr.RequestId);
+ }
+ }
+
+ public virtual bool IsHttpHandler (string verb, string uri)
+ {
+ string cacheKey = verb + "_" + uri;
+
+ bool locked = false;
+ try {
+ handlersCacheLock.EnterReadLock ();
+
+ locked = true;
+ if (handlersCache != null) {
+ bool found;
+ if (handlersCache.TryGetValue (cacheKey, out found))
+ return found;
+ } else {
+ handlersCache = new Dictionary <string, bool> ();
+ }
+ } finally {
+ if (locked)
+ handlersCacheLock.ExitReadLock ();
+ }
+
+
+ bool handlerFound = LocateHandler (verb, uri);
+ locked = false;
+ try {
+ handlersCacheLock.EnterWriteLock ();
+
+ locked = true;
+ if (handlersCache.ContainsKey (cacheKey))
+ handlersCache [cacheKey] = handlerFound;
+ else
+ handlersCache.Add (cacheKey, handlerFound);
+ } finally {
+ if (locked)
+ handlersCacheLock.ExitWriteLock ();
+ }
+
+ return handlerFound;
+ }
+
+ bool LocateHandler (string verb, string uri)
+ {
+ var config = WebConfigurationManager.GetSection ("system.web/httpHandlers") as HttpHandlersSection;
+ HttpHandlerActionCollection handlers = config != null ? config.Handlers : null;
+ int count = handlers != null ? handlers.Count : 0;
+
+ if (count == 0)
+ return false;
+
+ for (int i = 0; i < count; i++) {
+ HttpHandlerAction handler = handlers [i];
+ string[] verbs = SplitVerbs (handler.Verb);
+
+ if (verbs == null) {
+ if (PathMatches (handler, uri))
+ return true;
+ continue;
+ }
+
+ for (int j = 0; j < verbs.Length; j++) {
+ if (verbs [j] != verb)
+ continue;
+ if (PathMatches (handler, uri))
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ bool PathMatches (HttpHandlerAction handler, string uri)
+ {
+ bool result = false;
+ string[] handlerPaths = handler.Path.Split (',');
+ int slash = uri.LastIndexOf ('/');
+ string origUri = uri;
+ if (slash != -1)
+ uri = uri.Substring (slash);
+
+ SearchPattern sp = null;
+ foreach (string handlerPath in handlerPaths) {
+ if (handlerPath == "*")
+ continue; // ignore
+
+ string matchExact = null;
+ string endsWith = null;
+
+ if (handlerPath.Length > 0) {
+ if (handlerPath [0] == '*' && (handlerPath.IndexOf ('*', 1) == -1))
+ endsWith = handlerPath.Substring (1);
+
+ if (handlerPath.IndexOf ('*') == -1)
+ if (handlerPath [0] != '/') {
+ string vpath = HttpRuntime.AppDomainAppVirtualPath;
+
+ if (vpath == "/")
+ vpath = String.Empty;
+
+ matchExact = String.Concat (vpath, "/", handlerPath);
+ }
+ }
+
+ if (matchExact != null) {
+ result = matchExact.Length == origUri.Length && origUri.EndsWith (matchExact, StringComparison.OrdinalIgnoreCase);
+ if (result)
+ break;
+ continue;
+ }
+ if (endsWith != null) {
+ result = uri.EndsWith (endsWith, StringComparison.OrdinalIgnoreCase);
+ if (result)
+ break;
+ continue;
+ }
+
+ string pattern;
+ if (handlerPath.Length > 0 && handlerPath [0] == '/')
+ pattern = handlerPath.Substring (1);
+ else
+ pattern = handlerPath;
+
+ if (sp == null)
+ sp = new SearchPattern (pattern, true);
+ else
+ sp.SetPattern (pattern, true);
+
+ if (sp.IsMatch (origUri)) {
+ result = true;
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ static string[] SplitVerbs (string verb)
+ {
+ return verb == "*" ? null : verb.Split (',');
+ }
+ }
+}
+
diff --git a/src/Mono.WebServer/BaseRequestBroker.cs b/src/Mono.WebServer/BaseRequestBroker.cs
new file mode 100644
index 0000000..70602d7
--- /dev/null
+++ b/src/Mono.WebServer/BaseRequestBroker.cs
@@ -0,0 +1,268 @@
+//
+// Mono.WebServer.BaseRequestBroker
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo at ximian.com)
+// Lluis Sanchez Gual (lluis at ximian.com)
+//
+// (C) Copyright 2004-2010 Novell, Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+using System;
+
+namespace Mono.WebServer
+{
+ public class BaseRequestBroker: MarshalByRefObject, IRequestBroker
+ {
+ public event UnregisterRequestEventHandler UnregisterRequestEvent;
+
+ // Contains the initial request capacity of a BaseRequestBroker
+ const int INITIAL_REQUESTS = 200;
+
+ // The size of a request buffer in bytes.
+ //
+ // This number should be equal to INPUT_BUFFER_SIZE
+ // in System.Web.HttpRequest.
+ const int BUFFER_SIZE = 32*1024;
+
+ // Contains a lock to use when accessing and modifying the
+ // request allocation tables.
+ static readonly object reqlock = new object();
+
+ // Contains the request ID's.
+ int[] request_ids = new int [INITIAL_REQUESTS];
+
+ // Contains the registered workers.
+ Worker[] requests = new Worker [INITIAL_REQUESTS];
+
+ // Contains buffers for the requests to use.
+ byte[][] buffers = new byte [INITIAL_REQUESTS][];
+
+ // Contains the number of active requests.
+ int requests_count;
+
+ // Contains the total number of requests served so far.
+ // May freely wrap around.
+ uint requests_served;
+
+ /// <summary>
+ /// Grows the size of the request allocation tables by 33%.
+ ///
+ /// This *MUST* be called with the reqlock held!
+ /// </summary>
+ /// <returns>ID to use for a new request.</returns>
+ /// <param name="curlen">Current length of the allocation tables.</param>
+ int GrowRequests (ref int curlen)
+ {
+ int newsize = curlen + curlen/3;
+ var new_request_ids = new int [newsize];
+ var new_requests = new Worker [newsize];
+ var new_buffers = new byte [newsize][];
+
+ request_ids.CopyTo (new_request_ids, 0);
+ Array.Clear (request_ids, 0, request_ids.Length);
+ request_ids = new_request_ids;
+
+ requests.CopyTo (new_requests, 0);
+ Array.Clear (requests, 0, requests.Length);
+ requests = new_requests;
+
+ buffers.CopyTo (new_buffers, 0);
+ Array.Clear (buffers, 0, buffers.Length);
+ buffers = new_buffers;
+
+ curlen = newsize;
+ return curlen + 1;
+ }
+
+ /// <summary>
+ /// Gets the next available request ID, expanding the array
+ /// of possible ID's if necessary.
+ ///
+ /// This *MUST* be called with the reqlock held!
+ /// </summary>
+ /// <returns>ID of the request.</returns>
+ int GetNextRequestId ()
+ {
+ int reqlen = request_ids.Length;
+
+ requests_served++; // increment to 1 before putting into request_ids
+ // so that the 0 id is reserved for slot not used
+ if (requests_served == 0x8000) // and check for wrap-around for the above
+ requests_served = 1; // making sure we don't exceed 0x7FFF or go negative
+
+ requests_count++;
+
+ int newid;
+ if (requests_count >= reqlen)
+ newid = GrowRequests (ref reqlen);
+ else
+ newid = Array.IndexOf (request_ids, 0);
+
+ if (newid == -1) {
+ // Should never happen...
+ throw new ApplicationException ("could not allocate new request id");
+ }
+
+ // TODO: newid had better not exceed 0xFFFF.
+ newid = ((ushort)newid & 0xFFFF) | (((ushort)requests_served & 0x7FFF) << 16);
+ request_ids [IdToIndex(newid)] = newid;
+ return newid;
+ }
+
+ public int RegisterRequest (Worker worker)
+ {
+ int result;
+
+ lock (reqlock) {
+ result = IdToIndex (GetNextRequestId ());
+ requests [result] = worker;
+
+ // Don't create a new array if one already exists.
+ byte[] a = buffers [result];
+ if (a == null)
+ buffers [result] = new byte [BUFFER_SIZE];
+ }
+
+ return request_ids [result];
+ }
+
+ int IdToIndex(int requestId) {
+ return requestId & 0xFFFF;
+ }
+
+ public void UnregisterRequest (int id)
+ {
+ lock (reqlock) {
+ if (!ValidRequest (id))
+ return;
+
+ DoUnregisterRequest (id);
+ int idx = IdToIndex (id);
+
+ byte[] a = buffers [idx];
+ if (a != null)
+ Array.Clear (a, 0, a.Length);
+ requests [idx] = null;
+ request_ids [idx] = 0;
+ requests_count--;
+ }
+ }
+
+ /// <summary>
+ /// Invokes registered handlers of UnregisterRequestEvent. Each handler is passed an
+ /// arguments object which contains the ID of a request that is about to be
+ /// unregistered.
+ /// </summary>
+ /// <param name="id">ID of a request that is about to be unregistered.</param>
+ void DoUnregisterRequest (int id)
+ {
+ if (UnregisterRequestEvent == null)
+ return;
+ Delegate[] handlers = UnregisterRequestEvent.GetInvocationList ();
+ if (handlers == null || handlers.Length == 0)
+ return;
+
+ var args = new UnregisterRequestEventArgs (id);
+ foreach (UnregisterRequestEventHandler handler in handlers)
+ handler (this, args);
+ }
+
+ protected bool ValidRequest (int requestId)
+ {
+ int idx = IdToIndex (requestId);
+ return (idx >= 0 && idx < request_ids.Length && request_ids [idx] == requestId &&
+ buffers [idx] != null);
+ }
+
+ public int Read (int requestId, int size, out byte[] buffer)
+ {
+ buffer = null;
+
+ Worker w;
+
+ lock (reqlock) {
+ if (!ValidRequest (requestId))
+ return 0;
+
+ w = GetWorker (requestId);
+ if (w == null)
+ return 0;
+
+ // Use a pre-allocated buffer only when the size matches
+ // as it will be transferred across appdomain boundaries
+ // in full length
+ if (size == BUFFER_SIZE) {
+ buffer = buffers [IdToIndex (requestId)];
+ } else {
+ buffer = new byte[size];
+ }
+ }
+
+ return w.Read (buffer, 0, size);
+ }
+
+ public Worker GetWorker (int requestId)
+ {
+ lock (reqlock) {
+ if (!ValidRequest (requestId))
+ return null;
+
+ return requests [IdToIndex (requestId)];
+ }
+ }
+
+ public void Write (int requestId, byte[] buffer, int position, int size)
+ {
+ Worker worker = GetWorker (requestId);
+ if (worker != null)
+ worker.Write (buffer, position, size);
+ }
+
+ public void Close (int requestId)
+ {
+ Worker worker = GetWorker (requestId);
+ if (worker != null)
+ worker.Close ();
+ }
+
+ public void Flush (int requestId)
+ {
+ Worker worker = GetWorker (requestId);
+ if (worker != null)
+ worker.Flush ();
+ }
+
+ public bool IsConnected (int requestId)
+ {
+ Worker worker = GetWorker (requestId);
+
+ return (worker != null && worker.IsConnected ());
+ }
+
+ public override object InitializeLifetimeService ()
+ {
+ return null;
+ }
+ }
+}
diff --git a/src/Mono.WebServer/EndOfRequestHandler.cs b/src/Mono.WebServer/EndOfRequestHandler.cs
new file mode 100644
index 0000000..59dd1a6
--- /dev/null
+++ b/src/Mono.WebServer/EndOfRequestHandler.cs
@@ -0,0 +1,39 @@
+//
+// Mono.WebServer.EndOfRequestHandler
+//
+// Authors:
+// Daniel Lopez Ridruejo
+// Gonzalo Paniagua Javier
+//
+// Documentation:
+// Brian Nickel
+//
+// Copyright (c) 2002 Daniel Lopez Ridruejo.
+// (c) 2002,2003 Ximian, Inc.
+// All rights reserved.
+// (C) Copyright 2004-2010 Novell, Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+namespace Mono.WebServer
+{
+ public delegate void EndOfRequestHandler (MonoWorkerRequest request);
+}
diff --git a/src/Mono.WebServer/FinePlatformID.cs b/src/Mono.WebServer/FinePlatformID.cs
new file mode 100644
index 0000000..bc4b064
--- /dev/null
+++ b/src/Mono.WebServer/FinePlatformID.cs
@@ -0,0 +1,35 @@
+//
+// FinePlatformID.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+namespace Mono.WebServer {
+ public enum FinePlatformID {
+ Windows,
+ Linux,
+ MacOSX
+ }
+}
\ No newline at end of file
diff --git a/src/Mono.WebServer/HttpErrors.cs b/src/Mono.WebServer/HttpErrors.cs
new file mode 100644
index 0000000..93bfefc
--- /dev/null
+++ b/src/Mono.WebServer/HttpErrors.cs
@@ -0,0 +1,80 @@
+//
+// HttpErrors.cs
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo at ximian.com)
+// Lluis Sanchez Gual (lluis at ximian.com)
+//
+// Copyright (c) Copyright 2002-2007 Novell, Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+using System;
+using System.Text;
+
+namespace Mono.WebServer
+{
+ public class HttpErrors
+ {
+ static readonly byte [] error500;
+ static readonly byte [] badRequest;
+
+ static HttpErrors ()
+ {
+ const string s = "HTTP/1.0 500 Server error\r\n" +
+ "Connection: close\r\n\r\n" +
+ "<html><head><title>500 Server Error</title><body><h1>Server error</h1>\r\n" +
+ "Your client sent a request that was not understood by this server.\r\n" +
+ "</body></html>\r\n";
+ error500 = Encoding.ASCII.GetBytes (s);
+
+ const string br = "HTTP/1.0 400 Bad Request\r\n" +
+ "Connection: close\r\n\r\n" +
+ "<html><head><title>400 Bad Request</title></head>" +
+ "<body><h1>Bad Request</h1>The request was not understood" +
+ "<p></body></html>";
+
+ badRequest = Encoding.ASCII.GetBytes (br);
+ }
+
+ public static byte [] NotFound (string uri)
+ {
+ string s = String.Format ("HTTP/1.0 404 Not Found\r\n" +
+ "Connection: close\r\n\r\n" +
+ "<html><head><title>404 Not Found</title></head>\r\n" +
+ "<body><h1>Not Found</h1>The requested URL {0} was not found on this " +
+ "server.<p>\r\n</body></html>\r\n", uri);
+
+ return Encoding.ASCII.GetBytes (s);
+ }
+
+ public static byte [] BadRequest ()
+ {
+ return badRequest;
+ }
+
+ public static byte [] ServerError ()
+ {
+ return error500;
+ }
+ }
+}
diff --git a/src/Mono.WebServer/IApplicationHost.cs b/src/Mono.WebServer/IApplicationHost.cs
new file mode 100644
index 0000000..3e6db1b
--- /dev/null
+++ b/src/Mono.WebServer/IApplicationHost.cs
@@ -0,0 +1,48 @@
+//
+// Mono.WebServer.IApplicationHost
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo at ximian.com)
+//
+// Documentation:
+// Brian Nickel
+//
+// (C) 2003 Ximian, Inc (http://www.ximian.com)
+// (C) Copyright 2004-2010 Novell, Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+using System;
+
+namespace Mono.WebServer
+{
+ public interface IApplicationHost
+ {
+ string Path { get; }
+ string VPath { get; }
+ AppDomain Domain { get; }
+ IRequestBroker RequestBroker { get; set; }
+ ApplicationServer Server { get; set; }
+ void Unload ();
+ bool IsHttpHandler (string verb, string uri);
+ }
+}
diff --git a/src/Mono.WebServer/IRequestBroker.cs b/src/Mono.WebServer/IRequestBroker.cs
new file mode 100644
index 0000000..834bde1
--- /dev/null
+++ b/src/Mono.WebServer/IRequestBroker.cs
@@ -0,0 +1,38 @@
+//
+// Mono.WebServer.IRequestBroker
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo at ximian.com)
+//
+// Documentation:
+// Brian Nickel
+//
+// (C) 2003 Ximian, Inc (http://www.ximian.com)
+// (C) Copyright 2004-2010 Novell, Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+namespace Mono.WebServer
+{
+ public interface IRequestBroker
+ {
+ }
+}
diff --git a/src/Mono.WebServer/IdentityToken.cs b/src/Mono.WebServer/IdentityToken.cs
new file mode 100644
index 0000000..400d069
--- /dev/null
+++ b/src/Mono.WebServer/IdentityToken.cs
@@ -0,0 +1,51 @@
+//
+// IdentityToken.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using Mono.Unix.Native;
+
+namespace Mono.WebServer
+{
+ public class IdentityToken : IDisposable
+ {
+ readonly uint euid;
+ readonly uint egid;
+
+ public IdentityToken (uint euid, uint egid)
+ {
+ this.euid = euid;
+ this.egid = egid;
+ }
+
+ public void Dispose ()
+ {
+ Syscall.seteuid (euid);
+ Syscall.setegid (egid);
+ }
+ }
+}
diff --git a/src/Mono.WebServer/InitialWorkerRequest.cs b/src/Mono.WebServer/InitialWorkerRequest.cs
new file mode 100644
index 0000000..d3f7336
--- /dev/null
+++ b/src/Mono.WebServer/InitialWorkerRequest.cs
@@ -0,0 +1,281 @@
+//
+// Mono.WebServer.InitialWorkerRequest
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo at ximian.com)
+//
+// (C) 2003 Ximian, Inc (http://www.ximian.com)
+// (C) Copyright 2004-2010 Novell, Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Web;
+
+namespace Mono.WebServer
+{
+ public class InitialWorkerRequest
+ {
+ string verb;
+ string path;
+ string queryString;
+ string protocol;
+ readonly Stream stream;
+
+ int inputLength;
+ int position;
+ const int B_SIZE = 1024 * 32;
+
+ static readonly Stack<byte[]> bufferStack = new Stack<byte[]> ();
+ static readonly Encoding encoding = Encoding.GetEncoding (28591);
+
+ public bool GotSomeInput { get; private set; }
+
+ public byte [] InputBuffer { get; private set; }
+
+ public RequestData RequestData {
+ get {
+ var rd = new RequestData (verb, path, queryString, protocol);
+ var buffer = new byte [inputLength - position];
+ Buffer.BlockCopy (InputBuffer, position, buffer, 0, inputLength - position);
+ rd.InputBuffer = buffer;
+ return rd;
+ }
+ }
+
+ public static byte [] AllocateBuffer ()
+ {
+ lock (bufferStack) {
+ if (bufferStack.Count != 0)
+ return bufferStack.Pop ();
+ }
+ return new byte [B_SIZE];
+ }
+
+ public static void FreeBuffer (byte [] buf)
+ {
+ if (buf == null)
+ return;
+
+ lock (bufferStack) {
+ bufferStack.Push (buf);
+ }
+ }
+
+ public InitialWorkerRequest (Stream ns)
+ {
+ if (ns == null)
+ throw new ArgumentNullException ("ns");
+
+ stream = ns;
+ }
+
+ public void FreeBuffer ()
+ {
+ if (InputBuffer != null)
+ FreeBuffer (InputBuffer);
+ }
+
+ public void SetBuffer (byte [] buffer, int length)
+ {
+ InputBuffer = buffer;
+ inputLength = length;
+ GotSomeInput = (length > 0);
+ position = 0;
+ }
+
+ void FillBuffer ()
+ {
+ position = 0;
+ InputBuffer = AllocateBuffer ();
+ inputLength = stream.Read (InputBuffer, 0, B_SIZE);
+ if (inputLength == 0) // Socket closed
+ throw new IOException ("socket closed");
+
+ GotSomeInput = true;
+ }
+
+ string ReadRequestLine ()
+ {
+ var text = new StringBuilder ();
+ do {
+ if (InputBuffer == null || position >= inputLength)
+ FillBuffer ();
+
+ if (position >= inputLength)
+ break;
+
+ bool cr = false;
+ int count = 0;
+ byte b = 0;
+ int i;
+ for (i = position; count < 8192 && i < inputLength; i++, count++) {
+ b = InputBuffer [i];
+ if (b == '\r') {
+ cr = true;
+ count--;
+ continue;
+ }
+ if (b == '\n' || cr) {
+ count--;
+ break;
+ }
+ }
+
+ if (position >= inputLength && b == '\r' || b == '\n')
+ count++;
+
+ if (count >= 8192 || count + text.Length >= 8192)
+ throw new InvalidOperationException ("Line too long.");
+
+ if (count <= 0) {
+ position = i + 1;
+ break;
+ }
+
+ text.Append (encoding.GetString (InputBuffer, position, count));
+ position = i + 1;
+
+ if (i >= inputLength) {
+ b = InputBuffer [inputLength - 1];
+ if (b != '\r' && b != '\n')
+ continue;
+ FillBuffer();
+ if (b == '\r' && inputLength > 0 && InputBuffer[0] == '\n')
+ position++;
+ }
+ break;
+ } while (true);
+
+ if (text.Length == 0) {
+ return null;
+ }
+
+ return text.ToString ();
+ }
+
+ bool GetRequestLine ()
+ {
+ string req;
+ try {
+ while (true) {
+ req = ReadRequestLine ();
+ if (req == null) {
+ GotSomeInput = false;
+ return false;
+ }
+
+ req = req.Trim ();
+ // Ignore empty lines before the actual request.
+ if (req.Length > 0)
+ break;
+ }
+ } catch (Exception) {
+ GotSomeInput = false;
+ return false;
+ }
+
+ string [] s = req.Split (' ');
+
+ switch (s.Length) {
+ case 2:
+ verb = s [0].Trim ();
+ path = s [1].Trim ();
+ break;
+ case 3:
+ verb = s [0].Trim ();
+ path = s [1].Trim ();
+ protocol = s [2].Trim ();
+ break;
+ default:
+ return false;
+ }
+
+ int qmark = path.IndexOf ('?');
+ if (qmark != -1) {
+ queryString = path.Substring (qmark + 1);
+ path = path.Substring (0, qmark);
+ }
+
+ path = GetSafePath (path);
+ if (path.StartsWith ("/~/")) {
+ // Not sure about this. It makes request such us /~/dir/file work
+ path = path.Substring (2);
+ }
+
+ return true;
+ }
+
+ static string GetSafePath (string path)
+ {
+ bool appendSlash = path.EndsWith ("/");
+
+ path = HttpUtility.UrlDecode (path);
+ path = path.Replace ('\\','/');
+ while (path.IndexOf ("//") != -1)
+ path = path.Replace ("//", "/");
+
+ string [] parts = path.Split ('/');
+ var result = new List<string> (parts.Length);
+
+ int end = parts.Length;
+ for (int i = 0; i < end; i++) {
+ string current = parts [i];
+ if (current.Length == 0 || current == "." )
+ continue;
+
+ if (current == "..") {
+ if (result.Count > 0)
+ result.RemoveAt (result.Count - 1);
+ continue;
+ }
+
+ result.Add (current);
+ }
+
+ if (result.Count == 0)
+ return "/";
+
+ var res = new StringBuilder();
+ foreach (var part in result) {
+ res.Append ('/');
+ res.Append (part);
+ }
+
+ if (appendSlash)
+ res.Append ('/');
+ return res.ToString ();
+ }
+
+ public void ReadRequestData ()
+ {
+ if (!GetRequestLine ())
+ throw new RequestLineException ();
+
+ if (protocol == null) {
+ protocol = "HTTP/1.0";
+ }
+ }
+ }
+}
+
diff --git a/src/Mono.WebServer/LingeringNetworkStream.cs b/src/Mono.WebServer/LingeringNetworkStream.cs
new file mode 100644
index 0000000..e4f4b38
--- /dev/null
+++ b/src/Mono.WebServer/LingeringNetworkStream.cs
@@ -0,0 +1,100 @@
+//
+// Mono.WebServer.LingeringNetworkStream
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo at ximian.com)
+//
+// (C) Copyright 2004 Novell, Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Net.Sockets;
+
+namespace Mono.WebServer
+{
+ public class LingeringNetworkStream : NetworkStream
+ {
+ const int USECONDS_TO_LINGER = 2000000;
+ const int MAX_USECONDS_TO_LINGER = 30000000;
+ // We dont actually use the data from this buffer. So we cache it...
+ static byte [] buffer;
+
+ public LingeringNetworkStream (Socket sock, bool owns) : base (sock, owns)
+ {
+ EnableLingering = true;
+ OwnsSocket = owns;
+ }
+
+ public bool OwnsSocket { get; private set; }
+
+ public bool EnableLingering { get; set; }
+
+ void LingeringClose ()
+ {
+ int waited = 0;
+
+ if (!Connected)
+ return;
+
+ try {
+ Socket.Shutdown (SocketShutdown.Send);
+ DateTime start = DateTime.UtcNow;
+ while (waited < MAX_USECONDS_TO_LINGER) {
+ int nread = 0;
+ try {
+ if (!Socket.Poll (USECONDS_TO_LINGER, SelectMode.SelectRead))
+ break;
+
+ if (buffer == null)
+ buffer = new byte [512];
+
+ nread = Socket.Receive (buffer, 0, buffer.Length, 0);
+ } catch { }
+
+ if (nread == 0)
+ break;
+
+ waited += (int) (DateTime.UtcNow - start).TotalMilliseconds * 1000;
+ }
+ } catch {
+ // ignore - we don't care, we're closing anyway
+ }
+ }
+
+ public override void Close ()
+ {
+ if (EnableLingering) {
+ try {
+ LingeringClose ();
+ } finally {
+ base.Close ();
+ }
+ }
+ else
+ base.Close ();
+ }
+
+ public bool Connected {
+ get { return Socket.Connected; }
+ }
+ }
+}
diff --git a/src/Mono.WebServer/LockRecursionException.cs b/src/Mono.WebServer/LockRecursionException.cs
new file mode 100644
index 0000000..fc14431
--- /dev/null
+++ b/src/Mono.WebServer/LockRecursionException.cs
@@ -0,0 +1,54 @@
+/*
+ * System.Threading.LockRecursionException
+ *
+ * Author(s)
+ * Marek Safar <marek.safar at gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+using System.Runtime.Serialization;
+
+namespace System.Threading
+{
+ [Serializable]
+ class LockRecursionException : Exception
+ {
+ public LockRecursionException ()
+ {
+ }
+
+ public LockRecursionException (string message)
+ : base (message)
+ {
+ }
+
+ public LockRecursionException (string message, Exception e)
+ : base (message, e)
+ {
+ }
+
+ protected LockRecursionException (SerializationInfo info, StreamingContext sc)
+ : base (info, sc)
+ {
+ }
+ }
+}
diff --git a/src/Mono.WebServer/LockRecursionPolicy.cs b/src/Mono.WebServer/LockRecursionPolicy.cs
new file mode 100644
index 0000000..0437387
--- /dev/null
+++ b/src/Mono.WebServer/LockRecursionPolicy.cs
@@ -0,0 +1,36 @@
+/*
+ * System.Threading.LockRecursionPolicy
+ *
+ * Author(s)
+ * Marek Safar <marek.safar at gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+namespace System.Threading
+{
+ [Serializable]
+ enum LockRecursionPolicy
+ {
+ NoRecursion,
+ SupportsRecursion
+ }
+}
diff --git a/src/Mono.WebServer/Log/FileLogger.cs b/src/Mono.WebServer/Log/FileLogger.cs
new file mode 100644
index 0000000..e43642e
--- /dev/null
+++ b/src/Mono.WebServer/Log/FileLogger.cs
@@ -0,0 +1,86 @@
+//
+// FileLogger.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+
+namespace Mono.WebServer.Log {
+ class FileLogger : ILogger {
+ StreamWriter writer;
+
+ readonly object write_lock = new object ();
+
+ ~FileLogger ()
+ {
+ Close ();
+ }
+
+ public void Open (string path)
+ {
+ if (path == null)
+ throw new ArgumentNullException ("path");
+
+ lock (write_lock) {
+ Close ();
+ Stream stream = File.Open (path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
+ stream.Seek (0, SeekOrigin.End);
+ writer = new StreamWriter (stream);
+ }
+ }
+
+ public void Write (LogLevel level, string text)
+ {
+ if (writer == null)
+ return;
+
+ lock (write_lock) {
+ if (writer == null)
+ return;
+
+ writer.WriteLine (text);
+ writer.Flush ();
+ }
+ }
+
+ public void Close ()
+ {
+ lock (write_lock) {
+ if (writer == null)
+ return;
+
+ try {
+ writer.Flush ();
+ writer.Close ();
+ } catch (ObjectDisposedException) {
+ // Already done
+ }
+ writer = null;
+ }
+ }
+ }
+}
diff --git a/src/Mono.WebServer/Log/ILogger.cs b/src/Mono.WebServer/Log/ILogger.cs
new file mode 100644
index 0000000..cc26d29
--- /dev/null
+++ b/src/Mono.WebServer/Log/ILogger.cs
@@ -0,0 +1,35 @@
+//
+// ILogger.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+namespace Mono.WebServer.Log
+{
+ public interface ILogger
+ {
+ void Write (LogLevel level, string text);
+ }
+}
diff --git a/src/Mono.WebServer/Log/LogLevel.cs b/src/Mono.WebServer/Log/LogLevel.cs
new file mode 100644
index 0000000..61a348d
--- /dev/null
+++ b/src/Mono.WebServer/Log/LogLevel.cs
@@ -0,0 +1,43 @@
+//
+// LogLevel.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.WebServer.Log {
+ [Flags]
+ public enum LogLevel
+ {
+ None = 0x00,
+ Error = 0x01,
+ Warning = 0x02,
+ Notice = 0x04,
+ Debug = 0x08,
+ Standard = Error | Warning | Notice,
+ All = Error | Warning | Notice | Debug
+ }
+}
\ No newline at end of file
diff --git a/src/Mono.WebServer/Log/Logger.cs b/src/Mono.WebServer/Log/Logger.cs
new file mode 100644
index 0000000..6254de2
--- /dev/null
+++ b/src/Mono.WebServer/Log/Logger.cs
@@ -0,0 +1,151 @@
+//
+// Logger.cs: Logs server events.
+//
+// Author:
+// Brian Nickel (brian.nickel at gmail.com)
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (C) 2007 Brian Nickel
+// Copyright (C) 2013 Leonardo Taglialegne
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Threading;
+using System.Diagnostics;
+
+namespace Mono.WebServer.Log {
+ public static class Logger
+ {
+ static readonly List<ILogger> loggers = new List<ILogger> ();
+ static readonly FileLogger file_logger = new FileLogger ();
+
+ static readonly object write_lock = new object ();
+
+ #region Public Static Properties
+
+ public static LogLevel Level { get; set; }
+
+ public static bool WriteToConsole { get; set; }
+
+ public static bool Verbose { get; set; }
+
+ public static string Name { get; set; }
+
+ static int? ThreadId {
+ get {
+ if (Verbose && (Level & LogLevel.Debug) != LogLevel.None)
+ return Thread.CurrentThread.ManagedThreadId;
+ return null;
+ }
+ }
+
+
+ static int? ProcessId {
+ get {
+ if (Verbose && (Level & LogLevel.Debug) != LogLevel.None)
+ return Process.GetCurrentProcess ().Id;
+ return null;
+ }
+ }
+ #endregion
+
+ static Logger ()
+ {
+ Level = LogLevel.Standard;
+ loggers.Add (file_logger);
+ }
+
+ #region Public Static Methods
+
+ public static void AddLogger (ILogger logger)
+ {
+ loggers.Add (logger);
+ }
+
+ public static void Open (string path)
+ {
+ file_logger.Open (path);
+ }
+
+ public static void Write (LogLevel level, string format, params object [] args)
+ {
+ Write (level, CultureInfo.CurrentCulture, format, args);
+ }
+
+ public static void Write (Exception e)
+ {
+ if (e == null)
+ throw new ArgumentNullException ("e");
+ Write (LogLevel.Error, e.Message);
+ if(e.StackTrace != null)
+ foreach(var line in e.StackTrace.Split(new []{Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries))
+ Write (LogLevel.Error, line);
+ }
+
+ static string GetFormatString ()
+ {
+ return ThreadId == null
+ ? "[{2}] {3,-7}: {4}"
+ : Name == null
+ ? "{1,2} [{2}] {3,-7}: {4}"
+ : "{0,5}:{1,2} {5,-8} [{2}] {3,-7}: {4}";
+ }
+
+ public static void Write (LogLevel level, string message)
+ {
+ if ((Level & level) == LogLevel.None)
+ return;
+
+ var format = GetFormatString ();
+ string time = Verbose
+ ? DateTime.Now.ToString ("yyyy-MM-dd HH:mm:ss.ffffff")
+ : DateTime.Now.ToString ("yyyy-MM-dd HH:mm:ss");
+ string text = String.Format (CultureInfo.CurrentCulture, format, ProcessId, ThreadId, time, level, message, Name);
+
+ if (WriteToConsole)
+ lock (write_lock)
+ if (Verbose)
+ Console.WriteLine (text);
+ else
+ Console.WriteLine (message);
+
+ foreach(var logger in loggers)
+ logger.Write (level, text);
+ }
+
+ static void Write (LogLevel level,
+ IFormatProvider provider,
+ string format, params object [] args)
+ {
+ Write (level, String.Format (provider, format, args));
+ }
+
+ public static void Close ()
+ {
+ file_logger.Close ();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Mono.WebServer/Makefile.am b/src/Mono.WebServer/Makefile.am
index 1a19cb2..911642e 100644
--- a/src/Mono.WebServer/Makefile.am
+++ b/src/Mono.WebServer/Makefile.am
@@ -11,7 +11,7 @@ GACUTIL4=$(GACUTIL) -package 4.5
noinst_SCRIPTS= $(monowebserver4_install)
CLEANFILES = 4.0/Mono.WebServer2.dll*
-EXTRA_DIST = AssemblyInfo4.cs.in
+EXTRA_DIST = $(sources) AssemblyInfo4.cs.in Mono.WebServer.sources
sources = $(shell cat $(srcdir)/Mono.WebServer.sources)
diff --git a/src/Mono.WebServer/Makefile.in b/src/Mono.WebServer/Makefile.in
index 851de46..bd4cd70 100644
--- a/src/Mono.WebServer/Makefile.in
+++ b/src/Mono.WebServer/Makefile.in
@@ -287,7 +287,7 @@ monowebserver4_references = -r:System.Web.dll -r:System.Configuration.dll -r:Mon
GACUTIL4 = $(GACUTIL) -package 4.5
noinst_SCRIPTS = $(monowebserver4_install)
CLEANFILES = 4.0/Mono.WebServer2.dll*
-EXTRA_DIST = AssemblyInfo4.cs.in
+EXTRA_DIST = $(sources) AssemblyInfo4.cs.in Mono.WebServer.sources
sources = $(shell cat $(srcdir)/Mono.WebServer.sources)
monowebserver4_build_sources = $(addprefix $(srcdir)/, $(sources)) $(addprefix $(top_builddir)/src/Mono.WebServer/, AssemblyInfo4.cs)
all: all-am
diff --git a/src/Mono.WebServer/MapPathEventArgs.cs b/src/Mono.WebServer/MapPathEventArgs.cs
new file mode 100644
index 0000000..660cbcb
--- /dev/null
+++ b/src/Mono.WebServer/MapPathEventArgs.cs
@@ -0,0 +1,67 @@
+//
+// Mono.WebServer.MapPathEventArgs
+//
+// Authors:
+// Daniel Lopez Ridruejo
+// Gonzalo Paniagua Javier
+//
+// Documentation:
+// Brian Nickel
+//
+// Copyright (c) 2002 Daniel Lopez Ridruejo.
+// (c) 2002,2003 Ximian, Inc.
+// All rights reserved.
+// (C) Copyright 2004-2010 Novell, Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.WebServer
+{
+ public class MapPathEventArgs : EventArgs
+ {
+ // Contains the virtual path, as used in the request.
+
+ // Contains the physical "mapped" path.
+ string mapped;
+
+ // Indicates whether or not the path has been mapped.
+
+ public MapPathEventArgs (string path)
+ {
+ Path = path;
+ IsMapped = false;
+ }
+
+ public string Path { get; private set; }
+
+ public bool IsMapped { get; private set; }
+
+ public string MappedPath {
+ get { return mapped; }
+ set {
+ mapped = value;
+ IsMapped = !String.IsNullOrEmpty (value);
+ }
+ }
+ }
+}
diff --git a/src/Mono.WebServer/MapPathEventHandler.cs b/src/Mono.WebServer/MapPathEventHandler.cs
new file mode 100644
index 0000000..5688225
--- /dev/null
+++ b/src/Mono.WebServer/MapPathEventHandler.cs
@@ -0,0 +1,39 @@
+//
+// Mono.WebServer.MapPathEventHandler
+//
+// Authors:
+// Daniel Lopez Ridruejo
+// Gonzalo Paniagua Javier
+//
+// Documentation:
+// Brian Nickel
+//
+// Copyright (c) 2002 Daniel Lopez Ridruejo.
+// (c) 2002,2003 Ximian, Inc.
+// All rights reserved.
+// (C) Copyright 2004-2010 Novell, Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+namespace Mono.WebServer
+{
+ public delegate void MapPathEventHandler (object sender, MapPathEventArgs args);
+}
diff --git a/src/Mono.WebServer/Mono.WebServer.sources b/src/Mono.WebServer/Mono.WebServer.sources
new file mode 100644
index 0000000..98dde7a
--- /dev/null
+++ b/src/Mono.WebServer/Mono.WebServer.sources
@@ -0,0 +1,53 @@
+./ApplicationServer.cs
+./BaseApplicationHost.cs
+./BaseRequestBroker.cs
+./EndOfRequestHandler.cs
+./FinePlatformID.cs
+./HttpErrors.cs
+./IApplicationHost.cs
+./IdentityToken.cs
+./InitialWorkerRequest.cs
+./IRequestBroker.cs
+./LingeringNetworkStream.cs
+./LockRecursionException.cs
+./LockRecursionPolicy.cs
+./Log/FileLogger.cs
+./Log/ILogger.cs
+./Log/Logger.cs
+./Log/LogLevel.cs
+./MapPathEventArgs.cs
+./MapPathEventHandler.cs
+./MonoWorkerRequest.cs
+./Options/ConfigurationManager.cs
+./Options/ConfigurationManagerExtensions.cs
+./Options/ConfigurationManager.Fields.cs
+./Options/Descriptions.cs
+./Options/IHelpConfigurationManager.cs
+./Options/Options.cs
+./Options/ServerConfigurationManager.cs
+./Options/Settings/BoolSetting.cs
+./Options/Settings/EnumSetting.cs
+./Options/Settings/Int32Setting.cs
+./Options/Settings/ISetting.cs
+./Options/Settings/NullableInt32Setting.cs
+./Options/Settings/NullableSetting.cs
+./Options/Settings/NullableUInt16Setting.cs
+./Options/Settings/Parser.cs
+./Options/Settings/Setting.cs
+./Options/Settings/SettingsCollection.cs
+./Options/Settings/SettingSource.cs
+./Options/Settings/StringSetting.cs
+./Options/Settings/UInt16Setting.cs
+./Options/Settings/UInt32Setting.cs
+./Paths.cs
+./Platform.cs
+./RequestData.cs
+./RequestLineException.cs
+./SearchPattern.cs
+./UnregisterRequestEventArgs.cs
+./UnregisterRequestEventHandler.cs
+./Version.cs
+./VPathToHost.cs
+./WebSource.cs
+./WebTrace.cs
+./Worker.cs
diff --git a/src/Mono.WebServer/MonoWorkerRequest.cs b/src/Mono.WebServer/MonoWorkerRequest.cs
new file mode 100644
index 0000000..de2c738
--- /dev/null
+++ b/src/Mono.WebServer/MonoWorkerRequest.cs
@@ -0,0 +1,626 @@
+//
+// Mono.WebServer.MonoWorkerRequest
+//
+// Authors:
+// Daniel Lopez Ridruejo
+// Gonzalo Paniagua Javier
+//
+// Documentation:
+// Brian Nickel
+//
+// Copyright (c) 2002 Daniel Lopez Ridruejo.
+// (c) 2002,2003 Ximian, Inc.
+// All rights reserved.
+// (C) Copyright 2004-2010 Novell, Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Specialized;
+using System.Configuration;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Web;
+using System.Web.Hosting;
+using Mono.WebServer.Log;
+
+namespace Mono.WebServer
+{
+ public abstract class MonoWorkerRequest : SimpleWorkerRequest
+ {
+ const string DEFAULT_EXCEPTION_HTML = "<html><head><title>Runtime Error</title></head><body>An exception ocurred:<pre>{0}</pre></body></html>";
+ static readonly char[] mapPathTrimStartChars = { '/' };
+
+ static bool checkFileAccess = true;
+ readonly static bool needToReplacePathSeparator;
+ readonly static char pathSeparatorChar;
+
+ readonly IApplicationHost appHostBase;
+ Encoding encoding;
+ Encoding headerEncoding;
+ byte [] queryStringBytes;
+ string hostVPath;
+ string hostPath;
+ string hostPhysicalRoot;
+ EndOfSendNotification end_send;
+ object end_send_data;
+ X509Certificate client_cert;
+ NameValueCollection server_variables;
+ bool inUnhandledException;
+
+ // as we must have the client certificate (if provided) then we're able to avoid
+ // pre-calculating some items (and cache them if we have to calculate)
+ string cert_cookie;
+ string cert_issuer;
+ string cert_serial;
+ string cert_subject;
+
+ protected byte[] server_raw;
+ protected byte[] client_raw;
+
+ public event MapPathEventHandler MapPathEvent;
+ public event EndOfRequestHandler EndOfRequestEvent;
+
+ public abstract int RequestId { get; }
+
+ protected static bool RunningOnWindows { get; private set; }
+
+ public static bool CheckFileAccess {
+ get { return checkFileAccess; }
+ set { checkFileAccess = value; }
+ }
+
+ // Gets the physical path of the application host of the
+ // current instance.
+ string HostPath {
+ get {
+ if (hostPath == null)
+ hostPath = appHostBase.Path;
+
+ return hostPath;
+ }
+ }
+
+ // Gets the virtual path of the application host of the
+ // current instance.
+ string HostVPath {
+ get {
+ if (hostVPath == null)
+ hostVPath = appHostBase.VPath;
+
+ return hostVPath;
+ }
+ }
+
+ string HostPhysicalRoot {
+ get {
+ if (hostPhysicalRoot == null)
+ hostPhysicalRoot = appHostBase.Server.PhysicalRoot;
+
+ return hostPhysicalRoot;
+ }
+ }
+
+ protected virtual Encoding Encoding {
+ get {
+ if (encoding == null)
+ encoding = Encoding.GetEncoding (28591);
+
+ return encoding;
+ }
+ set {
+ encoding = value;
+ }
+ }
+
+ protected virtual Encoding HeaderEncoding {
+ get {
+ if (headerEncoding == null) {
+ HttpContext ctx = HttpContext.Current;
+ HttpResponse response = ctx != null ? ctx.Response : null;
+ Encoding enc = inUnhandledException ? null :
+ response != null ? response.HeaderEncoding : null;
+ headerEncoding = enc ?? Encoding;
+ }
+ return headerEncoding;
+ }
+ }
+
+ static MonoWorkerRequest ()
+ {
+ PlatformID pid = Environment.OSVersion.Platform;
+ RunningOnWindows = ((int) pid != 128 && pid != PlatformID.Unix && pid != PlatformID.MacOSX);
+
+ if (Path.DirectorySeparatorChar != '/') {
+ needToReplacePathSeparator = true;
+ pathSeparatorChar = Path.DirectorySeparatorChar;
+ }
+
+ try {
+ string v = ConfigurationManager.AppSettings ["MonoServerCheckHiddenFiles"];
+ if (!String.IsNullOrEmpty (v)) {
+ if (!Boolean.TryParse (v, out checkFileAccess))
+ checkFileAccess = true;
+ }
+ } catch (Exception) {
+ // ignore
+ checkFileAccess = true;
+ }
+ }
+
+ protected MonoWorkerRequest (IApplicationHost appHost)
+ : base (String.Empty, String.Empty, null)
+ {
+ if (appHost == null)
+ throw new ArgumentNullException ("appHost");
+
+ appHostBase = appHost;
+ }
+
+ public override string GetAppPath ()
+ {
+ return HostVPath;
+ }
+
+ public override string GetAppPathTranslated ()
+ {
+ return HostPath;
+ }
+
+ public override string GetFilePathTranslated ()
+ {
+ return MapPath (GetFilePath ());
+ }
+
+ public override string GetLocalAddress ()
+ {
+ return "localhost";
+ }
+
+ public override string GetServerName ()
+ {
+ string hostHeader = GetKnownRequestHeader(HeaderHost);
+ if (String.IsNullOrEmpty (hostHeader)) {
+ hostHeader = GetLocalAddress ();
+ } else {
+ int colonIndex = hostHeader.IndexOf (':');
+ if (colonIndex > 0) {
+ hostHeader = hostHeader.Substring (0, colonIndex);
+ } else if (colonIndex == 0) {
+ hostHeader = GetLocalAddress ();
+ }
+ }
+ return hostHeader;
+ }
+
+ public override int GetLocalPort ()
+ {
+ return 0;
+ }
+
+ public override byte [] GetPreloadedEntityBody ()
+ {
+ return null;
+ }
+
+ public override byte [] GetQueryStringRawBytes ()
+ {
+ if (queryStringBytes == null) {
+ string queryString = GetQueryString ();
+ if (queryString != null)
+ queryStringBytes = Encoding.GetBytes (queryString);
+ }
+
+ return queryStringBytes;
+ }
+
+ // Invokes the registered delegates one by one until the path is mapped.
+ //
+ // Parameters:
+ // path = virutal path of the request.
+ //
+ // Returns a string containing the mapped physical path of the request, or null if
+ // the path was not successfully mapped.
+ //
+ string DoMapPathEvent (string path)
+ {
+ if (MapPathEvent != null) {
+ var args = new MapPathEventArgs (path);
+ foreach (MapPathEventHandler evt in MapPathEvent.GetInvocationList ()) {
+ evt (this, args);
+ if (args.IsMapped)
+ return args.MappedPath;
+ }
+ }
+
+ return null;
+ }
+
+ // The logic here is as follows:
+ //
+ // If path is equal to the host's virtual path (including trailing slash),
+ // return the host virtual path.
+ //
+ // If path is absolute (starts with '/') then check if it's under our host vpath. If
+ // it is, base the mapping under the virtual application's physical path. If it
+ // isn't use the physical root of the application server to return the mapped
+ // path. If you have just one application configured, then the values computed in
+ // both of the above cases will be the same. If you have several applications
+ // configured for this xsp/mod-mono-server instance, then virtual paths outside our
+ // application virtual path will return physical paths relative to the server's
+ // physical root, not application's. This is consistent with the way IIS worker
+ // request works. See bug #575600
+ //
+ public override string MapPath (string path)
+ {
+ string eventResult = DoMapPathEvent (path);
+ if (eventResult != null)
+ return eventResult;
+
+ string hostVPath = HostVPath;
+ int hostVPathLen = HostVPath.Length;
+ int pathLen = path != null ? path.Length : 0;
+#if NET_2_0
+ bool inThisApp = path.StartsWith (hostVPath, StringComparison.Ordinal);
+#else
+ bool inThisApp = path.StartsWith (hostVPath);
+#endif
+ if (pathLen == 0 || (inThisApp && (pathLen == hostVPathLen || (pathLen == hostVPathLen + 1 && path [pathLen - 1] == '/')))) {
+ if (needToReplacePathSeparator)
+ return HostPath.Replace ('/', pathSeparatorChar);
+ return HostPath;
+ }
+
+ string basePath = null;
+ switch (path [0]) {
+ case '~':
+ if (path.Length >= 2 && path [1] == '/')
+ path = path.Substring (1);
+ break;
+
+ case '/':
+ if (!inThisApp)
+ basePath = HostPhysicalRoot;
+ break;
+ }
+
+ if (basePath == null)
+ basePath = HostPath;
+
+ if (inThisApp && (path.Length == hostVPathLen || path [hostVPathLen] == '/'))
+ path = path.Substring (hostVPathLen + 1);
+
+ path = path.TrimStart (mapPathTrimStartChars);
+ if (needToReplacePathSeparator)
+ path = path.Replace ('/', pathSeparatorChar);
+
+ return Path.Combine (basePath, path);
+ }
+
+ protected abstract bool GetRequestData ();
+
+ public bool ReadRequestData ()
+ {
+ return GetRequestData ();
+ }
+
+ static void LocationAccessible (string localPath)
+ {
+ bool doThrow = false;
+
+ if (RunningOnWindows) {
+ try {
+ var fi = new FileInfo (localPath);
+ FileAttributes attr = fi.Attributes;
+
+ if ((attr & FileAttributes.Hidden) != 0 || (attr & FileAttributes.System) != 0)
+ doThrow = true;
+ } catch (Exception) {
+ // ignore, will be handled in system.web
+ return;
+ }
+ } else {
+ // throw only if the file exists, let system.web handle the request
+ // otherwise
+ if (File.Exists (localPath) || Directory.Exists (localPath))
+ if (Path.GetFileName (localPath) [0] == '.')
+ doThrow = true;
+ }
+
+ if (doThrow)
+ throw new HttpException (403, "Forbidden.");
+ }
+
+ void AssertFileAccessible ()
+ {
+ if (!checkFileAccess)
+ return;
+
+ string localPath = GetFilePathTranslated ();
+ if (String.IsNullOrEmpty (localPath))
+ return;
+
+ char dirsep = Path.DirectorySeparatorChar;
+ string appPath = GetAppPathTranslated ();
+ string[] segments = localPath.Substring (appPath.Length).Split (dirsep);
+
+ var sb = new StringBuilder (appPath);
+ foreach (string s in segments) {
+ if (s.Length == 0)
+ continue;
+
+ if (s [0] != '.') {
+ sb.Append (s);
+ sb.Append (dirsep);
+ continue;
+ }
+
+ sb.Append (s);
+ LocationAccessible (sb.ToString ());
+ }
+ }
+
+ public void ProcessRequest ()
+ {
+ string error = null;
+ inUnhandledException = false;
+
+ try {
+ AssertFileAccessible ();
+ HttpRuntime.ProcessRequest (this);
+ } catch (HttpException ex) {
+ inUnhandledException = true;
+ error = ex.GetHtmlErrorMessage ();
+ } catch (Exception ex) {
+ inUnhandledException = true;
+ var hex = new HttpException (400, "Bad request", ex);
+ error = hex.GetHtmlErrorMessage ();
+ }
+
+ if (!inUnhandledException)
+ return;
+
+ if (error.Length == 0)
+ error = String.Format (DEFAULT_EXCEPTION_HTML, "Unknown error");
+
+ try {
+ SendStatus (400, "Bad request");
+ SendUnknownResponseHeader ("Connection", "close");
+ SendUnknownResponseHeader ("Date", DateTime.Now.ToUniversalTime ().ToString ("r"));
+
+ Encoding enc = Encoding.UTF8;
+
+ byte[] bytes = enc.GetBytes (error);
+
+ SendUnknownResponseHeader ("Content-Type", "text/html; charset=" + enc.WebName);
+ SendUnknownResponseHeader ("Content-Length", bytes.Length.ToString ());
+ SendResponseFromMemory (bytes, bytes.Length);
+ FlushResponse (true);
+ } catch (Exception ex) { // should "never" happen
+ Logger.Write (LogLevel.Error, "Error while processing a request: ");
+ Logger.Write (ex);
+ throw;
+ }
+ }
+
+ public override void EndOfRequest ()
+ {
+ if (EndOfRequestEvent != null)
+ EndOfRequestEvent (this);
+
+ if (end_send != null)
+ end_send (this, end_send_data);
+ }
+
+ public override void SetEndOfSendNotification (EndOfSendNotification callback, object extraData)
+ {
+ end_send = callback;
+ end_send_data = extraData;
+ }
+
+ public override void SendCalculatedContentLength (int contentLength)
+ {
+ //FIXME: Should we ignore this for apache2?
+ SendUnknownResponseHeader ("Content-Length", contentLength.ToString ());
+ }
+
+ public override void SendKnownResponseHeader (int index, string value)
+ {
+ if (HeadersSent ())
+ return;
+
+ string headerName = GetKnownResponseHeaderName (index);
+ SendUnknownResponseHeader (headerName, value);
+ }
+
+ protected void SendFromStream (Stream stream, long offset, long length)
+ {
+ if (offset < 0 || length <= 0)
+ return;
+
+ long stLength = stream.Length;
+ if (offset + length > stLength)
+ length = stLength - offset;
+
+ if (offset > 0)
+ stream.Seek (offset, SeekOrigin.Begin);
+
+ var fileContent = new byte [8192];
+ int count = fileContent.Length;
+ while (length > 0 && (count = stream.Read (fileContent, 0, count)) != 0) {
+ SendResponseFromMemory (fileContent, count);
+ length -= count;
+ // Keep the System. prefix
+ count = (int) System.Math.Min (length, fileContent.Length);
+ }
+ }
+
+ public override void SendResponseFromFile (string filename, long offset, long length)
+ {
+ FileStream file = null;
+ try {
+ file = File.OpenRead (filename);
+ SendFromStream (file, offset, length);
+ } finally {
+ if (file != null)
+ file.Close ();
+ }
+ }
+
+ public override void SendResponseFromFile (IntPtr handle, long offset, long length)
+ {
+ Stream file = null;
+ try {
+ file = new FileStream (handle, FileAccess.Read);
+ SendFromStream (file, offset, length);
+ } finally {
+ if (file != null)
+ file.Close ();
+ }
+ }
+
+ public override string GetServerVariable (string name)
+ {
+ if (server_variables == null)
+ return String.Empty;
+
+ if (IsSecure ()) {
+ X509Certificate client = ClientCertificate;
+ switch (name) {
+ case "CERT_COOKIE":
+ if (cert_cookie == null) {
+ if (client == null)
+ cert_cookie = String.Empty;
+ else
+ cert_cookie = client.GetCertHashString ();
+ }
+ return cert_cookie;
+ case "CERT_ISSUER":
+ if (cert_issuer == null) {
+ if (client == null)
+ cert_issuer = String.Empty;
+ else
+ cert_issuer = client.Issuer;
+ }
+ return cert_issuer;
+ case "CERT_SERIALNUMBER":
+ if (cert_serial == null) {
+ if (client == null)
+ cert_serial = String.Empty;
+ else
+ cert_serial = client.GetSerialNumberString ();
+ }
+ return cert_serial;
+ case "CERT_SUBJECT":
+ if (cert_subject == null) {
+ if (client == null)
+ cert_subject = String.Empty;
+ else
+ cert_subject = client.Subject;
+ }
+ return cert_subject;
+ }
+ }
+
+ string s = server_variables [name];
+ return s ?? String.Empty;
+ }
+
+ public void AddServerVariable (string name, string value)
+ {
+ if (server_variables == null)
+ server_variables = new NameValueCollection ();
+
+ server_variables.Add (name, value);
+ }
+
+ #region Client Certificate Support
+
+ public X509Certificate ClientCertificate {
+ get {
+ if ((client_cert == null) && (client_raw != null))
+ client_cert = new X509Certificate (client_raw);
+ return client_cert;
+ }
+ }
+
+ public void SetClientCertificate (byte[] rawcert)
+ {
+ client_raw = rawcert;
+ }
+
+ public override byte[] GetClientCertificate ()
+ {
+ return client_raw;
+ }
+
+ public override byte[] GetClientCertificateBinaryIssuer ()
+ {
+ if (ClientCertificate == null)
+ return base.GetClientCertificateBinaryIssuer ();
+ // TODO: not 100% sure of the content
+ return new byte [0];
+ }
+
+ public override int GetClientCertificateEncoding ()
+ {
+ if (ClientCertificate == null)
+ return base.GetClientCertificateEncoding ();
+ return 0;
+ }
+
+ public override byte[] GetClientCertificatePublicKey ()
+ {
+ if (ClientCertificate == null)
+ return base.GetClientCertificatePublicKey ();
+ return ClientCertificate.GetPublicKey ();
+ }
+
+ public override DateTime GetClientCertificateValidFrom ()
+ {
+ if (ClientCertificate == null)
+ return base.GetClientCertificateValidFrom ();
+ return DateTime.Parse (ClientCertificate.GetEffectiveDateString ());
+ }
+
+ public override DateTime GetClientCertificateValidUntil ()
+ {
+ if (ClientCertificate == null)
+ return base.GetClientCertificateValidUntil ();
+ return DateTime.Parse (ClientCertificate.GetExpirationDateString ());
+ }
+
+ #endregion
+
+ protected static string[] SplitAndTrim (string list)
+ {
+ if (String.IsNullOrEmpty (list))
+ return new string[0];
+ return (from f in list.Split (',')
+ let trimmed = f.Trim ()
+ where trimmed.Length != 0
+ select trimmed).ToArray ();
+ }
+ }
+}
+
diff --git a/src/Mono.WebServer/Options/ConfigurationManager.Fields.cs b/src/Mono.WebServer/Options/ConfigurationManager.Fields.cs
new file mode 100644
index 0000000..0ea5e5f
--- /dev/null
+++ b/src/Mono.WebServer/Options/ConfigurationManager.Fields.cs
@@ -0,0 +1,137 @@
+//
+// ConfigurationManager.Fields.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using Mono.WebServer.Log;
+using Mono.WebServer.Options.Settings;
+
+namespace Mono.WebServer.Options {
+ public abstract partial class ConfigurationManager {
+ protected ConfigurationManager (string name)
+ {
+ settings = new SettingsCollection {help, version, verbose, printlog,
+ logFile, configFile, this.name,
+ loglevels};
+ this.name.MaybeUpdate (SettingSource.Default, name);
+ }
+
+ #region Baking fields
+ readonly BoolSetting help = new BoolSetting ("help", "Shows this help message and exits.", prototype: "?|h|help");
+ readonly BoolSetting version = new BoolSetting ("version", "Displays version information and exits.", "V|version");
+ readonly BoolSetting verbose = new BoolSetting ("verbose", "Prints extra messages. Mainly useful for debugging.", prototype: "v|verbose");
+ readonly BoolSetting printlog = new BoolSetting ("printlog", "Prints log messages to the console.", environment: "MONO_PRINTLOG|MONO_FCGI_PRINTLOG", defaultValue: true);
+
+ readonly StringSetting logFile = new StringSetting ("logfile", "Specifies a file to log events to.", "FastCgiLogFile", "MONO_LOGFILE|MONO_FCGI_LOGFILE");
+ readonly StringSetting configFile = new StringSetting ("configfile|config-file", Descriptions.ConfigFile);
+ readonly StringSetting name = new StringSetting ("name", "Specifies a name to print in the log");
+
+ readonly EnumSetting<LogLevel> loglevels = new EnumSetting<LogLevel> ("loglevels", Descriptions.LogLevels, "FastCgiLogLevels", "MONO_FCGI_LOGLEVELS", LogLevel.Standard);
+ #endregion
+
+ #region Typesafe properties
+ public bool Help {
+ get { return help; }
+ }
+ public bool Version {
+ get { return version; }
+ }
+ public bool Verbose {
+ get { return verbose; }
+ }
+ public bool PrintLog {
+ get { return printlog; }
+ }
+
+ public string LogFile {
+ get { return logFile; }
+ }
+ public string ConfigFile {
+ get { return configFile; }
+ }
+ public string Name {
+ get { return name; }
+ }
+
+ public LogLevel LogLevels {
+ get { return loglevels; }
+ }
+ #endregion
+
+ protected void Add (params ISetting[] settings)
+ {
+ if (settings == null)
+ throw new ArgumentNullException ("settings");
+ foreach (ISetting setting in settings)
+ this.settings.Add (setting);
+ }
+
+ public void SetupLogger ()
+ {
+ Logger.Level = LogLevels;
+ OpenLogFile ();
+ Logger.WriteToConsole = PrintLog;
+ Logger.Verbose = Verbose;
+ Logger.Name = Name;
+ }
+
+ void OpenLogFile ()
+ {
+ try {
+ if (LogFile != null)
+ Logger.Open (LogFile);
+ } catch (Exception e) {
+ Logger.Write (LogLevel.Error, "Error opening log file: {0}", e.Message);
+ Logger.Write (LogLevel.Warning, "Events will not be logged to file.");
+ }
+ }
+
+ /// <summary>
+ /// If a configfile option was specified, tries to load
+ /// the configuration file
+ /// </summary>
+ /// <returns>false on failure, true on success or
+ /// option not present</returns>
+ public bool LoadConfigFile ()
+ {
+ try {
+ if (ConfigFile != null)
+ if(!TryLoadXmlConfig (ConfigFile))
+ return false;
+ } catch (ApplicationException e) {
+ Logger.Write (LogLevel.Error, e.Message);
+ return false;
+ } catch (System.Xml.XmlException e) {
+ Logger.Write (LogLevel.Error,
+ "Error reading XML configuration: {0}",
+ e.Message);
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/src/Mono.WebServer/Options/ConfigurationManager.cs b/src/Mono.WebServer/Options/ConfigurationManager.cs
new file mode 100644
index 0000000..4af1bdf
--- /dev/null
+++ b/src/Mono.WebServer/Options/ConfigurationManager.cs
@@ -0,0 +1,144 @@
+//
+// ConfigurationManager.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Globalization;
+using System.IO;
+using System.Xml;
+using Mono.Unix;
+using Mono.WebServer.Log;
+using NDesk.Options;
+using Mono.WebServer.Options.Settings;
+
+namespace Mono.WebServer.Options {
+ public abstract partial class ConfigurationManager {
+ const string EXCEPT_BAD_ELEM = "XML setting \"{0}={1}\" is invalid.";
+
+ readonly SettingsCollection settings;
+
+ [Obsolete]
+ internal SettingsCollection Settings {
+ get { return settings; }
+ }
+
+
+ public bool TryLoadXmlConfig (string file)
+ {
+ if (String.IsNullOrEmpty (file))
+ throw new ArgumentNullException ("file");
+ var doc = new XmlDocument ();
+ try {
+ doc.Load (file);
+ } catch (FileNotFoundException e) {
+ Console.Error.WriteLine("ERROR: Couldn't find configuration file {0}!", e.FileName);
+ return false;
+ }
+ if (Platform.IsUnix) {
+ var fileInfo = new UnixFileInfo (file);
+ ImportSettings (doc, true, file, fileInfo.OwnerUser.UserName, fileInfo.OwnerGroup.GroupName);
+ } else
+ ImportSettings (doc, true, file);
+ return true;
+ }
+
+ [Obsolete]
+ public void ImportSettings (XmlDocument doc, bool insertEmptyValue)
+ {
+ ImportSettings (doc, insertEmptyValue, null);
+ }
+
+ void ImportSettings (XmlDocument doc, bool insertEmptyValue, string filename , string user = null, string group = null)
+ {
+ if (doc == null)
+ throw new ArgumentNullException ("doc");
+
+ var tags = doc.GetElementsByTagName ("Setting");
+ foreach (XmlElement setting in tags) {
+ string name = GetXmlValue (setting, "Name");
+ string value = Parse (GetXmlValue (setting, "Value"), filename, user, group);
+ if (name.Length == 0)
+ throw AppExcept (EXCEPT_BAD_ELEM, name, value);
+
+ if (settings.Contains (name)) {
+ if (insertEmptyValue || value.Length > 0)
+ settings [name].MaybeParseUpdate (SettingSource.Xml, value);
+ } else
+ Logger.Write (LogLevel.Warning, "Unrecognized xml setting: {0} with value {1}", name, value);
+ }
+ }
+
+ static string Parse (string value, string filename, string user, string group)
+ {
+ if (!String.IsNullOrEmpty (filename))
+ value = value.Replace ("$(filename)", Path.GetFileNameWithoutExtension (filename));
+ if (!String.IsNullOrEmpty (user))
+ value = value.Replace ("$(user)", user);
+ if (!String.IsNullOrEmpty (group))
+ value = value.Replace ("$(group)", group);
+ return value;
+ }
+
+ public OptionSet CreateOptionSet ()
+ {
+ var p = new OptionSet ();
+ foreach (ISetting setting in settings) {
+ var boolSetting = setting as Setting<bool>;
+ if (boolSetting != null) {
+ p.Add (setting.Prototype, setting.Description,
+ v => boolSetting.MaybeUpdate (SettingSource.CommandLine, v != null));
+ } else {
+ ISetting setting1 = setting; // Used in closure, must copy
+ p.Add (setting.Prototype + "=", setting.Description,
+ v => { if (v != null) setting1.MaybeParseUpdate (SettingSource.CommandLine, v); } );
+ }
+ }
+ return p;
+ }
+
+ static ApplicationException AppExcept (string message, params object [] args)
+ {
+ return new ApplicationException (String.Format (
+ CultureInfo.InvariantCulture, message, args));
+ }
+
+ static string GetXmlValue (XmlElement elem, string name)
+ {
+ string value = elem.GetAttribute (name);
+ if (!String.IsNullOrEmpty (value))
+ return value;
+
+ foreach (XmlElement child in elem.GetElementsByTagName (name)) {
+ value = child.InnerText;
+ if (!String.IsNullOrEmpty (value))
+ return value;
+ }
+
+ return String.Empty;
+ }
+ }
+}
diff --git a/src/Mono.WebServer/Options/ConfigurationManagerExtensions.cs b/src/Mono.WebServer/Options/ConfigurationManagerExtensions.cs
new file mode 100644
index 0000000..00044c9
--- /dev/null
+++ b/src/Mono.WebServer/Options/ConfigurationManagerExtensions.cs
@@ -0,0 +1,73 @@
+//
+// ConfigurationManagerExtensions.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using NDesk.Options;
+
+namespace Mono.WebServer.Options {
+ public static class ConfigurationManagerExtensions {
+
+ public static void PrintHelp<T> (this T configurationManager) where T : IHelpConfigurationManager
+ {
+ Version.Show ();
+ Console.WriteLine (configurationManager.Description);
+ Console.WriteLine ("Usage is:\n");
+ Console.WriteLine (" {0} [...]", configurationManager.ProgramName);
+ Console.WriteLine ();
+ configurationManager.CreateOptionSet ().WriteOptionDescriptions (Console.Out);
+ }
+
+ public static bool LoadCommandLineArgs<T> (this T configurationManager, string [] cmd_args) where T : IHelpConfigurationManager
+ {
+ if (cmd_args == null)
+ throw new ArgumentNullException ("cmd_args");
+
+ OptionSet optionSet = configurationManager.CreateOptionSet ();
+
+ List<string> extra;
+ try {
+ extra = optionSet.Parse (cmd_args);
+ } catch (OptionException e) {
+ Console.WriteLine ("{0}: {1}", configurationManager.ProgramName, e.Message);
+ Console.WriteLine ("Try `{0} --help' for more information.", configurationManager.ProgramName);
+ return false;
+ }
+
+ if (extra.Count > 0) {
+ Console.Write ("Warning: unparsed command line arguments: ");
+ foreach (string s in extra) {
+ Console.Write ("{0} ", s);
+ }
+ Console.WriteLine ();
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/Mono.WebServer/Options/Descriptions.cs b/src/Mono.WebServer/Options/Descriptions.cs
new file mode 100644
index 0000000..64638c1
--- /dev/null
+++ b/src/Mono.WebServer/Options/Descriptions.cs
@@ -0,0 +1,93 @@
+//
+// Descriptions.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+namespace Mono.WebServer.Options {
+ public static class Descriptions
+ {
+ public const string MaxConns = "Specifies the maximum number of concurrent connections the server should accept.";
+
+ public const string LogLevels = "Specifies what log levels to log. It can be any of the following values, or multiple if comma separated:\n" +
+ "* Debug\n" +
+ "* Notice\n" +
+ "* Warning\n" +
+ "* Error\n" +
+ "* Standard (Notice,Warning,Error)\n" +
+ "* All (Debug,Standard)\n" +
+ "This value is only used when \"logfile\" or \"printlog\" are set.";
+
+ public const string Socket = "Specifies the type of socket to listen on. Valid values are \"pipe\", \"unix\", and \"tcp\".\n" +
+ "\"pipe\" indicates to use piped socket opened by the web server when it spawns the application.\n" +
+ "\"unix\" indicates that a standard unix socket should be opened.\n" +
+ " The file name can be specified in the \"filename\" argument or appended to this argument with a colon, eg:\n" +
+ " unix\n unix:/tmp/fastcgi-mono-socket\n" +
+ "\"tcp\" indicates that a TCP socket should be opened. " +
+ " The address and port can be specified in the \"port\" and \"address\" arguments or appended to this argument with a colon, eg:\n" +
+ " tcp\n tcp:8081\n tcp:127.0.0.1:8081\n tcp:0.0.0.0:8081";
+
+ public const string AppConfigFile = "Adds application definitions from an XML configuration file, typically with the \".webapp\" extension. " +
+ "See sample configuration file that comes with the server.";
+
+ public const string AppConfigDir = "Adds application definitions from all XML files found in the specified directory. " +
+ "Files must have the \".webapp\" extension.";
+
+ public const string ConfigFile = "Specifies a file containing configuration options, identical to those available in the command line.";
+
+ public const string Stoppable = "Allows the user to stop the server by pressing \"Enter\". " +
+ "This should not be used when the server has no controlling terminal.";
+
+ internal const string Address = "Specifies the IP address to listen on.";
+
+ public const string Port = "Specifies the TCP port number to listen on.";
+
+ internal const string Root = "Specifies the root directory the server changes to before doing performing any operations.\n" +
+ "This value is only used when \"appconfigfile\", \"appconfigdir\", or \"applications\" is set, to provide a relative base path.";
+
+ // TODO: use markup (sigh) for better formatting
+ public const string Applications = "Adds applications from a comma separated list of virtual and physical directory pairs. " +
+ "The pairs are separated by colons and optionally include the virtual host name and port to use:\n" +
+ " [hostname:[port:]]VPath:realpath,...\n" +
+ "Samples:\n" +
+ " /:.\n" +
+ " The virtual root directory, \"/\", is mapped to the current directory or \"root\" if specified." +
+ " /blog:../myblog\n" +
+ " The virtual /blog is mapped to ../myblog\n" +
+ " myhost.someprovider.net:/blog:../myblog\n" +
+ " The virtual /blog at myhost.someprovider.net is mapped to ../myblog.\n" +
+ " This means that other host names, like \"localhost\" will not be mapped.\n" +
+ " /:.,/blog:../myblog\n" +
+ " Two applications like the above ones are handled.\n" +
+ " *:80:/:standard,*:433:/:secure\n" +
+ " The server uses different applications on the unsecure and secure ports.";
+
+ public const string Terminate = "Gracefully terminates a running mod-mono-server instance. " +
+ "All other options but --filename or --address and --port are ignored if this option is provided.";
+
+ public const string Master = "This instance will be used to by mod_mono to create ASP.NET applications on demand. " +
+ "If this option is provided, there is no need to provide a list of applications to start.";
+ }
+}
\ No newline at end of file
diff --git a/src/Mono.WebServer/Options/IHelpConfigurationManager.cs b/src/Mono.WebServer/Options/IHelpConfigurationManager.cs
new file mode 100644
index 0000000..2ce13da
--- /dev/null
+++ b/src/Mono.WebServer/Options/IHelpConfigurationManager.cs
@@ -0,0 +1,37 @@
+//
+// IHelpConfigurationManager.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using NDesk.Options;
+
+namespace Mono.WebServer.Options {
+ public interface IHelpConfigurationManager {
+ string ProgramName { get; }
+ string Description { get; }
+ OptionSet CreateOptionSet ();
+ }
+}
\ No newline at end of file
diff --git a/src/Mono.WebServer/Options/Options.cs b/src/Mono.WebServer/Options/Options.cs
new file mode 100644
index 0000000..dbd9b27
--- /dev/null
+++ b/src/Mono.WebServer/Options/Options.cs
@@ -0,0 +1,997 @@
+//
+// Options.cs
+//
+// Authors:
+// Jonathan Pryor <jpryor at novell.com>
+//
+// Copyright (C) 2008 Novell (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+//
+// A Getopt::Long-inspired option parsing library for C#.
+//
+// NDesk.Options.OptionSet is built upon a key/value table, where the
+// key is a option format string and the value is a delegate that is
+// invoked when the format string is matched.
+//
+// Option format strings:
+// Regex-like BNF Grammar:
+// name: .+
+// type: [=:]
+// sep: ( [^{}]+ | '{' .+ '}' )?
+// aliases: ( name type sep ) ( '|' name type sep )*
+//
+// Each '|'-delimited name is an alias for the associated action. If the
+// format string ends in a '=', it has a required value. If the format
+// string ends in a ':', it has an optional value. If neither '=' or ':'
+// is present, no value is supported. `=' or `:' need only be defined on one
+// alias, but if they are provided on more than one they must be consistent.
+//
+// Each alias portion may also end with a "key/value separator", which is used
+// to split option values if the option accepts > 1 value. If not specified,
+// it defaults to '=' and ':'. If specified, it can be any character except
+// '{' and '}' OR the *string* between '{' and '}'. If no separator should be
+// used (i.e. the separate values should be distinct arguments), then "{}"
+// should be used as the separator.
+//
+// Options are extracted either from the current option by looking for
+// the option name followed by an '=' or ':', or is taken from the
+// following option IFF:
+// - The current option does not contain a '=' or a ':'
+// - The current option requires a value (i.e. not a Option type of ':')
+//
+// The `name' used in the option format string does NOT include any leading
+// option indicator, such as '-', '--', or '/'. All three of these are
+// permitted/required on any named option.
+//
+// Option bundling is permitted so long as:
+// - '-' is used to start the option group
+// - all of the bundled options are a single character
+// - at most one of the bundled options accepts a value, and the value
+// provided starts from the next character to the end of the string.
+//
+// This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value'
+// as '-Dname=value'.
+//
+// Option processing is disabled by specifying "--". All options after "--"
+// are returned by OptionSet.Parse() unchanged and unprocessed.
+//
+// Unprocessed options are returned from OptionSet.Parse().
+//
+// Examples:
+// int verbose = 0;
+// OptionSet p = new OptionSet ()
+// .Add ("v", v => ++verbose)
+// .Add ("name=|value=", v => Console.WriteLine (v));
+// p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"});
+//
+// The above would parse the argument string array, and would invoke the
+// lambda expression three times, setting `verbose' to 3 when complete.
+// It would also print out "A" and "B" to standard output.
+// The returned array would contain the string "extra".
+//
+// C# 3.0 collection initializers are supported and encouraged:
+// var p = new OptionSet () {
+// { "h|?|help", v => ShowHelp () },
+// };
+//
+// System.ComponentModel.TypeConverter is also supported, allowing the use of
+// custom data types in the callback type; TypeConverter.ConvertFromString()
+// is used to convert the value option to an instance of the specified
+// type:
+//
+// var p = new OptionSet () {
+// { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) },
+// };
+//
+// Random other tidbits:
+// - Boolean options (those w/o '=' or ':' in the option format string)
+// are explicitly enabled if they are followed with '+', and explicitly
+// disabled if they are followed with '-':
+// string a = null;
+// var p = new OptionSet () {
+// { "a", s => a = s },
+// };
+// p.Parse (new string[]{"-a"}); // sets a != null
+// p.Parse (new string[]{"-a+"}); // sets a != null
+// p.Parse (new string[]{"-a-"}); // sets a == null
+//
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.IO;
+using System.Runtime.Serialization;
+using System.Security.Permissions;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace NDesk.Options {
+
+ public class OptionValueCollection : IList<string> {
+ readonly List<string> values = new List<string> ();
+ readonly OptionContext context;
+
+ internal OptionValueCollection (OptionContext context)
+ {
+ this.context = context;
+ }
+
+ #region ICollection<T>
+ public void Add (string item) {values.Add (item);}
+ public void Clear () {values.Clear ();}
+ public bool Contains (string item) {return values.Contains (item);}
+ public void CopyTo (string[] array, int arrayIndex) {values.CopyTo (array, arrayIndex);}
+ public bool Remove (string item) {return values.Remove (item);}
+ public int Count {get {return values.Count;}}
+ public bool IsReadOnly {get {return false;}}
+ #endregion
+
+ #region IEnumerable
+ IEnumerator IEnumerable.GetEnumerator () {return values.GetEnumerator ();}
+ #endregion
+
+ #region IEnumerable<T>
+ public IEnumerator<string> GetEnumerator () {return values.GetEnumerator ();}
+ #endregion
+
+ #region IList<T>
+ public int IndexOf (string item) {return values.IndexOf (item);}
+ public void Insert (int index, string item) {values.Insert (index, item);}
+ public void RemoveAt (int index) {values.RemoveAt (index);}
+
+ void AssertValid (int index)
+ {
+ if (context.Option == null)
+ throw new InvalidOperationException ("OptionContext.Option is null.");
+ if (index >= context.Option.MaxValueCount)
+ throw new ArgumentOutOfRangeException ("index");
+ if (context.Option.OptionValueType == OptionValueType.Required &&
+ index >= values.Count)
+ throw new OptionException (String.Format (
+ context.OptionSet.MessageLocalizer ("Missing required value for option '{0}'."), context.OptionName),
+ context.OptionName);
+ }
+
+ public string this [int index] {
+ get {
+ AssertValid (index);
+ return index >= values.Count ? null : values [index];
+ }
+ set {
+ values [index] = value;
+ }
+ }
+ #endregion
+
+ public List<string> ToList ()
+ {
+ return new List<string> (values);
+ }
+
+ public string[] ToArray ()
+ {
+ return values.ToArray ();
+ }
+
+ public override string ToString ()
+ {
+ return String.Join (", ", values.ToArray ());
+ }
+ }
+
+ public class OptionContext {
+ public OptionContext (OptionSet set)
+ {
+ OptionSet = set;
+ OptionValues = new OptionValueCollection (this);
+ }
+
+ public Option Option { get; set; }
+
+ public string OptionName { get; set; }
+
+ public int OptionIndex { get; set; }
+
+ public OptionSet OptionSet { get; private set; }
+
+ public OptionValueCollection OptionValues { get; private set; }
+ }
+
+ public enum OptionValueType {
+ None,
+ Optional,
+ Required,
+ }
+
+ public abstract class Option {
+ protected Option (string prototype, string description)
+ : this (prototype, description, 1)
+ {
+ }
+
+ protected Option (string prototype, string description, int maxValueCount)
+ {
+ if (prototype == null)
+ throw new ArgumentNullException ("prototype");
+ if (prototype.Length == 0)
+ throw new ArgumentException ("Cannot be the empty string.", "prototype");
+ if (maxValueCount < 0)
+ throw new ArgumentOutOfRangeException ("maxValueCount");
+
+ Prototype = prototype;
+ Description = description;
+ Names = prototype.Split ('|');
+ MaxValueCount = maxValueCount;
+ OptionValueType = ParsePrototype ();
+
+ if (MaxValueCount == 0 && OptionValueType != OptionValueType.None)
+ throw new ArgumentException (
+ "Cannot provide maxValueCount of 0 for OptionValueType.Required or " +
+ "OptionValueType.Optional.",
+ "maxValueCount");
+ if (OptionValueType == OptionValueType.None && maxValueCount > 1)
+ throw new ArgumentException (
+ String.Format ("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount),
+ "maxValueCount");
+ if (Array.IndexOf (Names, "<>") >= 0 &&
+ ((Names.Length == 1 && OptionValueType != OptionValueType.None) ||
+ (Names.Length > 1 && MaxValueCount > 1)))
+ throw new ArgumentException (
+ "The default option handler '<>' cannot require values.",
+ "prototype");
+ }
+
+ public string Prototype { get; private set; }
+ public string Description { get; private set; }
+ public OptionValueType OptionValueType { get; private set; }
+ public int MaxValueCount { get; private set; }
+
+ public string[] GetNames ()
+ {
+ return (string[]) Names.Clone ();
+ }
+
+ public string[] GetValueSeparators ()
+ {
+ if (ValueSeparators == null)
+ return new string [0];
+ return (string[]) ValueSeparators.Clone ();
+ }
+
+ protected static T Parse<T> (string value, OptionContext c)
+ {
+ TypeConverter conv = TypeDescriptor.GetConverter (typeof (T));
+ T t = default (T);
+ try {
+ if (value != null)
+ t = (T) conv.ConvertFromString (value);
+ }
+ catch (Exception e) {
+ throw new OptionException (
+ String.Format (
+ c.OptionSet.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."),
+ value, typeof (T).Name, c.OptionName),
+ c.OptionName, e);
+ }
+ return t;
+ }
+
+ internal string[] Names { get; private set; }
+ internal string[] ValueSeparators { get; private set; }
+
+ static readonly char[] nameTerminator = {'=', ':'};
+
+ OptionValueType ParsePrototype ()
+ {
+ char type = '\0';
+ var seps = new List<string> ();
+ for (int i = 0; i < Names.Length; ++i) {
+ string name = Names [i];
+ if (name.Length == 0)
+ throw new ArgumentException ("Empty option names are not supported.");
+
+ int end = name.IndexOfAny (nameTerminator);
+ if (end == -1)
+ continue;
+ Names [i] = name.Substring (0, end);
+ if (type == '\0' || type == name [end])
+ type = name [end];
+ else
+ throw new ArgumentException (
+ String.Format ("Conflicting option types: '{0}' vs. '{1}'.", type, name [end]));
+ AddSeparators (name, end, seps);
+ }
+
+ if (type == '\0')
+ return OptionValueType.None;
+
+ if (MaxValueCount <= 1 && seps.Count != 0)
+ throw new ArgumentException (
+ String.Format ("Cannot provide key/value separators for Options taking {0} value(s).", MaxValueCount));
+ if (MaxValueCount > 1) {
+ if (seps.Count == 0)
+ ValueSeparators = new[]{":", "="};
+ else if (seps.Count == 1 && seps [0].Length == 0)
+ ValueSeparators = null;
+ else
+ ValueSeparators = seps.ToArray ();
+ }
+
+ return type == '=' ? OptionValueType.Required : OptionValueType.Optional;
+ }
+
+ static void AddSeparators (string name, int end, ICollection<string> seps)
+ {
+ int start = -1;
+ for (int i = end+1; i < name.Length; ++i) {
+ switch (name [i]) {
+ case '{':
+ if (start != -1)
+ throw new ArgumentException (
+ String.Format ("Ill-formed name/value separator found in \"{0}\".", name),
+ "name");
+ start = i+1;
+ break;
+ case '}':
+ if (start == -1)
+ throw new ArgumentException (
+ String.Format ("Ill-formed name/value separator found in \"{0}\".", name),
+ "name");
+ seps.Add (name.Substring (start, i-start));
+ start = -1;
+ break;
+ default:
+ if (start == -1)
+ seps.Add (name [i].ToString ());
+ break;
+ }
+ }
+ if (start != -1)
+ throw new ArgumentException (
+ String.Format ("Ill-formed name/value separator found in \"{0}\".", name),
+ "name");
+ }
+
+ public void Invoke (OptionContext c)
+ {
+ OnParseComplete (c);
+ c.OptionName = null;
+ c.Option = null;
+ c.OptionValues.Clear ();
+ }
+
+ protected abstract void OnParseComplete (OptionContext c);
+
+ public override string ToString ()
+ {
+ return Prototype;
+ }
+ }
+
+ [Serializable]
+ public class OptionException : Exception {
+ public OptionException ()
+ {
+ }
+
+ public OptionException (string message, string optionName)
+ : base (message)
+ {
+ OptionName = optionName;
+ }
+
+ public OptionException (string message, string optionName, Exception innerException)
+ : base (message, innerException)
+ {
+ OptionName = optionName;
+ }
+
+ protected OptionException (SerializationInfo info, StreamingContext context)
+ : base (info, context)
+ {
+ OptionName = info.GetString ("OptionName");
+ }
+
+ public string OptionName { get; private set; }
+
+ [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)]
+ public override void GetObjectData (SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData (info, context);
+ info.AddValue ("OptionName", OptionName);
+ }
+ }
+
+ public delegate void OptionAction<in TKey, in TValue> (TKey key, TValue value);
+
+ public class OptionSet : KeyedCollection<string, Option>
+ {
+ public OptionSet ()
+ : this (f => f)
+ {
+ }
+
+ public OptionSet (Converter<string, string> localizer)
+ {
+ MessageLocalizer = localizer;
+ }
+
+ public Converter<string, string> MessageLocalizer { get; private set; }
+
+ protected override string GetKeyForItem (Option item)
+ {
+ if (item == null)
+ throw new ArgumentNullException ("item");
+ if (item.Names != null && item.Names.Length > 0)
+ return item.Names [0];
+ // This should never happen, as it's invalid for Option to be
+ // constructed w/o any names.
+ throw new InvalidOperationException ("Option has no names!");
+ }
+
+ [Obsolete ("Use KeyedCollection.this[string]")]
+ protected Option GetOptionForName (string option)
+ {
+ if (option == null)
+ throw new ArgumentNullException ("option");
+ try {
+ return base [option];
+ }
+ catch (KeyNotFoundException) {
+ return null;
+ }
+ }
+
+ protected override void InsertItem (int index, Option item)
+ {
+ base.InsertItem (index, item);
+ AddImpl (item);
+ }
+
+ protected override void RemoveItem (int index)
+ {
+ base.RemoveItem (index);
+ Option p = Items [index];
+ // KeyedCollection.RemoveItem() handles the 0th item
+ for (int i = 1; i < p.Names.Length; ++i) {
+ Dictionary.Remove (p.Names [i]);
+ }
+ }
+
+ protected override void SetItem (int index, Option item)
+ {
+ base.SetItem (index, item);
+ RemoveItem (index);
+ AddImpl (item);
+ }
+
+ void AddImpl (Option option)
+ {
+ if (option == null)
+ throw new ArgumentNullException ("option");
+ var added = new List<string> (option.Names.Length);
+ try {
+ // KeyedCollection.InsertItem/SetItem handle the 0th name.
+ for (int i = 1; i < option.Names.Length; ++i) {
+ Dictionary.Add (option.Names [i], option);
+ added.Add (option.Names [i]);
+ }
+ }
+ catch (Exception) {
+ foreach (string name in added)
+ Dictionary.Remove (name);
+ throw;
+ }
+ }
+
+ public new OptionSet Add (Option option)
+ {
+ base.Add (option);
+ return this;
+ }
+
+ sealed class ActionOption : Option {
+ readonly Action<OptionValueCollection> action;
+
+ public ActionOption (string prototype, string description, int count, Action<OptionValueCollection> action)
+ : base (prototype, description, count)
+ {
+ if (action == null)
+ throw new ArgumentNullException ("action");
+ this.action = action;
+ }
+
+ protected override void OnParseComplete (OptionContext c)
+ {
+ action (c.OptionValues);
+ }
+ }
+
+ public OptionSet Add (string prototype, Action<string> action)
+ {
+ return Add (prototype, null, action);
+ }
+
+ public OptionSet Add (string prototype, string description, Action<string> action)
+ {
+ if (action == null)
+ throw new ArgumentNullException ("action");
+ Option p = new ActionOption (prototype, description, 1,
+ v => action (v [0]));
+ base.Add (p);
+ return this;
+ }
+
+ public OptionSet Add (string prototype, OptionAction<string, string> action)
+ {
+ return Add (prototype, null, action);
+ }
+
+ public OptionSet Add (string prototype, string description, OptionAction<string, string> action)
+ {
+ if (action == null)
+ throw new ArgumentNullException ("action");
+ Option p = new ActionOption (prototype, description, 2,
+ v => action (v [0], v [1]));
+ base.Add (p);
+ return this;
+ }
+
+ sealed class ActionOption<T> : Option {
+ readonly Action<T> action;
+
+ public ActionOption (string prototype, string description, Action<T> action)
+ : base (prototype, description, 1)
+ {
+ if (action == null)
+ throw new ArgumentNullException ("action");
+ this.action = action;
+ }
+
+ protected override void OnParseComplete (OptionContext c)
+ {
+ action (Parse<T> (c.OptionValues [0], c));
+ }
+ }
+
+ sealed class ActionOption<TKey, TValue> : Option {
+ readonly OptionAction<TKey, TValue> action;
+
+ public ActionOption (string prototype, string description, OptionAction<TKey, TValue> action)
+ : base (prototype, description, 2)
+ {
+ if (action == null)
+ throw new ArgumentNullException ("action");
+ this.action = action;
+ }
+
+ protected override void OnParseComplete (OptionContext c)
+ {
+ action (
+ Parse<TKey> (c.OptionValues [0], c),
+ Parse<TValue> (c.OptionValues [1], c));
+ }
+ }
+
+ public OptionSet Add<T> (string prototype, Action<T> action)
+ {
+ return Add (prototype, null, action);
+ }
+
+ public OptionSet Add<T> (string prototype, string description, Action<T> action)
+ {
+ return Add (new ActionOption<T> (prototype, description, action));
+ }
+
+ public OptionSet Add<TKey, TValue> (string prototype, OptionAction<TKey, TValue> action)
+ {
+ return Add (prototype, null, action);
+ }
+
+ public OptionSet Add<TKey, TValue> (string prototype, string description, OptionAction<TKey, TValue> action)
+ {
+ return Add (new ActionOption<TKey, TValue> (prototype, description, action));
+ }
+
+ protected virtual OptionContext CreateOptionContext ()
+ {
+ return new OptionContext (this);
+ }
+
+ public List<string> Parse (IEnumerable<string> arguments)
+ {
+ OptionContext c = CreateOptionContext ();
+ c.OptionIndex = -1;
+ bool process = true;
+ var unprocessed = new List<string> ();
+ Option def = Contains ("<>") ? this ["<>"] : null;
+ foreach (string argument in arguments) {
+ ++c.OptionIndex;
+ if (argument == "--") {
+ process = false;
+ continue;
+ }
+ if (!process) {
+ Unprocessed (unprocessed, def, c, argument);
+ continue;
+ }
+ if (!Parse (argument, c))
+ Unprocessed (unprocessed, def, c, argument);
+ }
+ if (c.Option != null)
+ c.Option.Invoke (c);
+ return unprocessed;
+ }
+
+ static void Unprocessed (ICollection<string> extra, Option def, OptionContext c, string argument)
+ {
+ if (def == null) {
+ extra.Add (argument);
+ return;
+ }
+ c.OptionValues.Add (argument);
+ c.Option = def;
+ c.Option.Invoke (c);
+ }
+
+ readonly Regex valueOption = new Regex (
+ @"^(?<flag>--|-|/)(?<name>[^:=]+)((?<sep>[:=])(?<value>.*))?$");
+
+ protected bool GetOptionParts (string argument, out string flag, out string name, out string sep, out string value)
+ {
+ if (argument == null)
+ throw new ArgumentNullException ("argument");
+
+ flag = name = sep = value = null;
+ Match m = valueOption.Match (argument);
+ if (!m.Success) {
+ return false;
+ }
+ flag = m.Groups ["flag"].Value;
+ name = m.Groups ["name"].Value;
+ if (m.Groups ["sep"].Success && m.Groups ["value"].Success) {
+ sep = m.Groups ["sep"].Value;
+ value = m.Groups ["value"].Value;
+ }
+ return true;
+ }
+
+ protected virtual bool Parse (string argument, OptionContext c)
+ {
+ if (c.Option != null) {
+ ParseValue (argument, c);
+ return true;
+ }
+
+ string f, n, s, v;
+ if (!GetOptionParts (argument, out f, out n, out s, out v))
+ return false;
+
+ if (Contains (n)) {
+ Option p = this [n];
+ c.OptionName = f + n;
+ c.Option = p;
+ switch (p.OptionValueType) {
+ case OptionValueType.None:
+ c.OptionValues.Add (n);
+ c.Option.Invoke (c);
+ break;
+ case OptionValueType.Optional:
+ case OptionValueType.Required:
+ ParseValue (v, c);
+ break;
+ }
+ return true;
+ }
+ // no match; is it a bool option?
+ if (ParseBool (argument, n, c))
+ return true;
+ // is it a bundled option?
+ if (ParseBundledValue (f, String.Concat (n, s, v), c))
+ return true;
+
+ return false;
+ }
+
+ void ParseValue (string option, OptionContext c)
+ {
+ if (option != null)
+ foreach (string o in c.Option.ValueSeparators != null
+ ? option.Split (c.Option.ValueSeparators, StringSplitOptions.None)
+ : new[]{option}) {
+ c.OptionValues.Add (o);
+ }
+ if (c.OptionValues.Count == c.Option.MaxValueCount ||
+ c.Option.OptionValueType == OptionValueType.Optional)
+ c.Option.Invoke (c);
+ else if (c.OptionValues.Count > c.Option.MaxValueCount) {
+ throw new OptionException (MessageLocalizer (String.Format (
+ "Error: Found {0} option values when expecting {1}.",
+ c.OptionValues.Count, c.Option.MaxValueCount)),
+ c.OptionName);
+ }
+ }
+
+ bool ParseBool (string option, string n, OptionContext c)
+ {
+ string rn;
+ if (n.Length >= 1 && (n [n.Length-1] == '+' || n [n.Length-1] == '-') &&
+ Contains ((rn = n.Substring (0, n.Length-1)))) {
+ Option p = this [rn];
+ string v = n [n.Length-1] == '+' ? option : null;
+ c.OptionName = option;
+ c.Option = p;
+ c.OptionValues.Add (v);
+ p.Invoke (c);
+ return true;
+ }
+ return false;
+ }
+
+ bool ParseBundledValue (string f, string n, OptionContext c)
+ {
+ if (f != "-")
+ return false;
+ for (int i = 0; i < n.Length; ++i) {
+ string opt = f + n [i];
+ string rn = n [i].ToString ();
+ if (!Contains (rn)) {
+ if (i == 0)
+ return false;
+ throw new OptionException (String.Format (MessageLocalizer (
+ "Cannot bundle unregistered option '{0}'."), opt), opt);
+ }
+ Option p = this [rn];
+ switch (p.OptionValueType) {
+ case OptionValueType.None:
+ Invoke (c, opt, n, p);
+ break;
+ case OptionValueType.Optional:
+ case OptionValueType.Required: {
+ string v = n.Substring (i+1);
+ c.Option = p;
+ c.OptionName = opt;
+ ParseValue (v.Length != 0 ? v : null, c);
+ return true;
+ }
+ default:
+ throw new InvalidOperationException ("Unknown OptionValueType: " + p.OptionValueType);
+ }
+ }
+ return true;
+ }
+
+ static void Invoke (OptionContext c, string name, string value, Option option)
+ {
+ c.OptionName = name;
+ c.Option = option;
+ c.OptionValues.Add (value);
+ option.Invoke (c);
+ }
+
+ const int OPTION_WIDTH = 29;
+
+ public void WriteOptionDescriptions (TextWriter o)
+ {
+ foreach (Option p in this) {
+ int written = 0;
+ if (!WriteOptionPrototype (o, p, ref written))
+ continue;
+
+ if (written < OPTION_WIDTH)
+ o.Write (new string (' ', OPTION_WIDTH - written));
+ else {
+ o.WriteLine ();
+ o.Write (new string (' ', OPTION_WIDTH));
+ }
+
+ List<string> lines = GetLines (MessageLocalizer (GetDescription (p.Description)));
+ o.WriteLine (lines [0]);
+ var prefix = new string (' ', OPTION_WIDTH+2);
+ for (int i = 1; i < lines.Count; ++i) {
+ o.Write (prefix);
+ o.WriteLine (lines [i]);
+ }
+ }
+ }
+
+ bool WriteOptionPrototype (TextWriter o, Option p, ref int written)
+ {
+ string[] names = p.Names;
+
+ int i = GetNextOptionIndex (names, 0);
+ if (i == names.Length)
+ return false;
+
+ if (names [i].Length == 1) {
+ Write (o, ref written, " -");
+ Write (o, ref written, names [0]);
+ }
+ else {
+ Write (o, ref written, " --");
+ Write (o, ref written, names [0]);
+ }
+
+ for ( i = GetNextOptionIndex (names, i+1);
+ i < names.Length; i = GetNextOptionIndex (names, i+1)) {
+ Write (o, ref written, ", ");
+ Write (o, ref written, names [i].Length == 1 ? "-" : "--");
+ Write (o, ref written, names [i]);
+ }
+
+ if (p.OptionValueType == OptionValueType.Optional ||
+ p.OptionValueType == OptionValueType.Required) {
+ if (p.OptionValueType == OptionValueType.Optional) {
+ Write (o, ref written, MessageLocalizer ("["));
+ }
+ Write (o, ref written, MessageLocalizer ("=" + GetArgumentName (0, p.MaxValueCount, p.Description)));
+ string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0
+ ? p.ValueSeparators [0]
+ : " ";
+ for (int c = 1; c < p.MaxValueCount; ++c) {
+ Write (o, ref written, MessageLocalizer (sep + GetArgumentName (c, p.MaxValueCount, p.Description)));
+ }
+ if (p.OptionValueType == OptionValueType.Optional) {
+ Write (o, ref written, MessageLocalizer ("]"));
+ }
+ }
+ return true;
+ }
+
+ static int GetNextOptionIndex (string[] names, int i)
+ {
+ while (i < names.Length && names [i] == "<>") {
+ ++i;
+ }
+ return i;
+ }
+
+ static void Write (TextWriter o, ref int n, string s)
+ {
+ n += s.Length;
+ o.Write (s);
+ }
+
+ static string GetArgumentName (int index, int maxIndex, string description)
+ {
+ if (String.IsNullOrEmpty (description))
+ return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1);
+ string[] nameStart = maxIndex == 1
+ ? new[] { "{0:", "{" }
+ : new[] { "{" + index + ":" };
+ foreach (string name in nameStart) {
+ int start, j = 0;
+ do {
+ start = description.IndexOf (name, j);
+ } while (start >= 0 && j != 0 && description [j++ - 1] == '{');
+ if (start == -1)
+ continue;
+ int end = description.IndexOf ("}", start);
+ if (end == -1)
+ continue;
+ return description.Substring (start + name.Length, end - start - name.Length);
+ }
+ return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1);
+ }
+
+ static string GetDescription (string description)
+ {
+ if (String.IsNullOrEmpty (description))
+ return String.Empty;
+ var sb = new StringBuilder (description.Length);
+ int start = -1;
+ for (int i = 0; i < description.Length; ++i) {
+ switch (description [i]) {
+ case '{':
+ if (i == start) {
+ sb.Append ('{');
+ start = -1;
+ }
+ else if (start < 0)
+ start = i + 1;
+ break;
+ case '}':
+ if (start < 0) {
+ if ((i+1) == description.Length || description [i+1] != '}')
+ throw new InvalidOperationException ("Invalid option description: " + description);
+ ++i;
+ sb.Append ("}");
+ }
+ else {
+ sb.Append (description.Substring (start, i - start));
+ start = -1;
+ }
+ break;
+ case ':':
+ if (start < 0)
+ goto default;
+ start = i + 1;
+ break;
+ default:
+ if (start < 0)
+ sb.Append (description [i]);
+ break;
+ }
+ }
+ return sb.ToString ();
+ }
+
+ static List<string> GetLines (string description)
+ {
+ var lines = new List<string> ();
+ if (String.IsNullOrEmpty (description)) {
+ lines.Add (String.Empty);
+ return lines;
+ }
+ const int length = 80 - OPTION_WIDTH - 2;
+ int start = 0, end;
+ do {
+ end = GetLineEnd (start, length, description);
+ bool cont = false;
+ if (end < description.Length) {
+ char c = description [end];
+ if (c == '-' || (char.IsWhiteSpace (c) && c != '\n'))
+ ++end;
+ else if (c != '\n') {
+ cont = true;
+ --end;
+ }
+ }
+ lines.Add (description.Substring (start, end - start));
+ if (cont) {
+ lines [lines.Count-1] += "-";
+ }
+ start = end;
+ if (start < description.Length && description [start] == '\n')
+ ++start;
+ } while (end < description.Length);
+ return lines;
+ }
+
+ static int GetLineEnd (int start, int length, string description)
+ {
+ int end = Math.Min (start + length, description.Length);
+ int sep = -1;
+ for (int i = start; i < end; ++i) {
+ switch (description [i]) {
+ case ' ':
+ case '\t':
+ case '\v':
+ case '-':
+ case ',':
+ case '.':
+ case ';':
+ sep = i;
+ break;
+ case '\n':
+ return i;
+ }
+ }
+ if (sep == -1 || end == description.Length)
+ return end;
+ return sep;
+ }
+ }
+}
diff --git a/src/Mono.WebServer/Options/ServerConfigurationManager.cs b/src/Mono.WebServer/Options/ServerConfigurationManager.cs
new file mode 100644
index 0000000..15fa05b
--- /dev/null
+++ b/src/Mono.WebServer/Options/ServerConfigurationManager.cs
@@ -0,0 +1,78 @@
+//
+// ServerConfigurationManager.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Net;
+using Mono.WebServer.Options.Settings;
+
+namespace Mono.WebServer.Options {
+ public abstract class ServerConfigurationManager : ConfigurationManager, IHelpConfigurationManager
+ {
+ #region Backing fields
+ protected readonly StringSetting root = new StringSetting ("root", Descriptions.Root, "MonoServerRootDir", "MONO_ROOT|MONO_FCGI_ROOT", Environment.CurrentDirectory);
+ readonly StringSetting applications = new StringSetting ("applications", Descriptions.Applications, "MonoApplications", "MONO_APPLICATIONS|MONO_FCGI_APPLICATIONS");
+ readonly StringSetting appConfigFile = new StringSetting ("appconfigfile", Descriptions.AppConfigFile, "MonoApplicationsConfigFile", "MONO_APPCONFIGFILE|MONO_FCGI_APPCONFIGFILE");
+ readonly StringSetting appConfigDir = new StringSetting ("appconfigdir", Descriptions.AppConfigDir, "MonoApplicationsConfigDir", "MONO_APPCONFIGDIR|MONO_FCGI_APPCONFIGDIR");
+
+ readonly UInt32Setting backlog = new UInt32Setting ("backlog", "The listen backlog.", defaultValue: 500);
+
+ protected readonly Setting<IPAddress> address = new Setting<IPAddress> ("address", IPAddress.TryParse, Descriptions.Address, "MonoServerAddress", "MONO_ADDRESS|MONO_FCGI_ADDRESS", IPAddress.Loopback);
+ #endregion
+
+ #region Typesafe properties
+ public string Root {
+ get { return root; }
+ }
+ public string Applications {
+ get { return applications; }
+ }
+ public string AppConfigFile {
+ get { return appConfigFile; }
+ }
+ public string AppConfigDir {
+ get { return appConfigDir; }
+ }
+
+ public uint Backlog {
+ get { return backlog; }
+ }
+
+ public IPAddress Address { get { return address; } }
+ #endregion
+
+ public abstract string ProgramName { get; }
+ public abstract string Description { get; }
+
+ protected ServerConfigurationManager (string name) : base (name)
+ {
+ Add (root, applications, appConfigFile, appConfigDir,
+ backlog,
+ address);
+ }
+ }
+}
diff --git a/src/Mono.WebServer/Options/Settings/BoolSetting.cs b/src/Mono.WebServer/Options/Settings/BoolSetting.cs
new file mode 100644
index 0000000..153f0cc
--- /dev/null
+++ b/src/Mono.WebServer/Options/Settings/BoolSetting.cs
@@ -0,0 +1,39 @@
+//
+// BoolSetting.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.WebServer.Options.Settings {
+ public class BoolSetting : Setting<bool>
+ {
+ public BoolSetting (string name, string description, string appSetting = null, string environment = null, bool defaultValue = false, string prototype = null)
+ : base (name, Boolean.TryParse, description, appSetting, environment, defaultValue, prototype)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Mono.WebServer/Options/Settings/EnumSetting.cs b/src/Mono.WebServer/Options/Settings/EnumSetting.cs
new file mode 100644
index 0000000..94c8c8b
--- /dev/null
+++ b/src/Mono.WebServer/Options/Settings/EnumSetting.cs
@@ -0,0 +1,50 @@
+//
+// EnumSetting.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.WebServer.Options.Settings {
+ public class EnumSetting<T> : Setting<T> where T : struct
+ {
+ static bool EnumParser<TEnum> (string input, out TEnum output)
+ {
+ output = default (TEnum);
+ try {
+ output = (TEnum)Enum.Parse (typeof (TEnum), input, true);
+ return true;
+ } catch (ArgumentException) { // TODO: catch more specific type
+ return false;
+ }
+ }
+
+ public EnumSetting (string name, string description, string appSetting = null, string environment = null, T defaultValue = default(T), string prototype = null)
+ : base (name, EnumParser, description, appSetting, environment, defaultValue, prototype)
+ {
+ }
+ }
+}
diff --git a/src/Mono.WebServer/Options/Settings/ISetting.cs b/src/Mono.WebServer/Options/Settings/ISetting.cs
new file mode 100644
index 0000000..0bf2b54
--- /dev/null
+++ b/src/Mono.WebServer/Options/Settings/ISetting.cs
@@ -0,0 +1,44 @@
+//
+// ISetting.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.WebServer.Options.Settings {
+ public interface ISetting
+ {
+ string Name { get; }
+ string Description { get; }
+ string AppSetting { get; }
+ string Environment { get; }
+ string Prototype { get; }
+ [Obsolete]
+ object Value { get; }
+
+ void MaybeParseUpdate (SettingSource settingSource, string value);
+ }
+}
\ No newline at end of file
diff --git a/src/Mono.WebServer/Options/Settings/Int32Setting.cs b/src/Mono.WebServer/Options/Settings/Int32Setting.cs
new file mode 100644
index 0000000..fa637f6
--- /dev/null
+++ b/src/Mono.WebServer/Options/Settings/Int32Setting.cs
@@ -0,0 +1,39 @@
+//
+// Int32Setting.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.WebServer.Options.Settings {
+ public class Int32Setting : Setting<int>
+ {
+ public Int32Setting (string name, string description, string appSetting = null, string environment = null, int defaultValue = default(Int32), string prototype = null)
+ : base (name, Int32.TryParse, description, appSetting, environment, defaultValue, prototype)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Mono.WebServer/Options/Settings/NullableInt32Setting.cs b/src/Mono.WebServer/Options/Settings/NullableInt32Setting.cs
new file mode 100644
index 0000000..aca41d3
--- /dev/null
+++ b/src/Mono.WebServer/Options/Settings/NullableInt32Setting.cs
@@ -0,0 +1,39 @@
+//
+// NullableInt32Setting.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.WebServer.Options.Settings {
+ public class NullableInt32Setting : NullableSetting<int>
+ {
+ public NullableInt32Setting (string name, string description, string appSetting = null, string environment = null, int? defaultValue = null, string prototype = null)
+ : base (name, Int32.TryParse, description, appSetting, environment, defaultValue, prototype)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Mono.WebServer/Options/Settings/NullableSetting.cs b/src/Mono.WebServer/Options/Settings/NullableSetting.cs
new file mode 100644
index 0000000..5a27965
--- /dev/null
+++ b/src/Mono.WebServer/Options/Settings/NullableSetting.cs
@@ -0,0 +1,51 @@
+//
+// NullableSetting.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+namespace Mono.WebServer.Options.Settings {
+ public class NullableSetting<T>:Setting<T?> where T : struct
+ {
+ public NullableSetting (string name, Parser<T> parser, string description, string appSetting = null, string environment = null, T? defaultValue = null, string prototype = null)
+ : base (name, ToNullable(parser), description, appSetting, environment, defaultValue, prototype)
+ {
+ }
+
+ static Parser<T?> ToNullable (Parser<T> parser)
+ {
+ return delegate (string input, out T? output)
+ {
+ T temp;
+ if (!parser (input, out temp)) {
+ output = null;
+ return false;
+ }
+ output = temp;
+ return true;
+ };
+ }
+ }
+}
diff --git a/src/Mono.WebServer/Options/Settings/NullableUInt16Setting.cs b/src/Mono.WebServer/Options/Settings/NullableUInt16Setting.cs
new file mode 100644
index 0000000..2f367c1
--- /dev/null
+++ b/src/Mono.WebServer/Options/Settings/NullableUInt16Setting.cs
@@ -0,0 +1,39 @@
+//
+// NullableUInt16Setting.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.WebServer.Options.Settings {
+ public class NullableUInt16Setting : NullableSetting<ushort>
+ {
+ public NullableUInt16Setting (string name, string description, string appSetting = null, string environment = null, ushort? defaultValue = null, string prototype = null)
+ : base (name, UInt16.TryParse, description, appSetting, environment, defaultValue, prototype)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Mono.WebServer/Options/Settings/Parser.cs b/src/Mono.WebServer/Options/Settings/Parser.cs
new file mode 100644
index 0000000..dc6ef19
--- /dev/null
+++ b/src/Mono.WebServer/Options/Settings/Parser.cs
@@ -0,0 +1,31 @@
+//
+// Parser.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+namespace Mono.WebServer.Options.Settings {
+ public delegate bool Parser<T> (string input, out T output);
+}
\ No newline at end of file
diff --git a/src/Mono.WebServer/Options/Settings/Setting.cs b/src/Mono.WebServer/Options/Settings/Setting.cs
new file mode 100644
index 0000000..aebd5fa
--- /dev/null
+++ b/src/Mono.WebServer/Options/Settings/Setting.cs
@@ -0,0 +1,95 @@
+//
+// Settings.cs.
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialene at gmail.com>
+//
+// Copyright (C) 2013 Leonardo Taglialegne
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.WebServer.Options.Settings {
+ public class Setting<T> : ISetting
+ {
+ // TODO: Find a clean way to make this static
+ readonly Parser<T> parser;
+
+ public Setting (string name, Parser<T> parser, string description, string appSetting = null,
+ string environment = null, T defaultValue = default (T), string prototype = null)
+ {
+ Prototype = prototype ?? name;
+ Description = description;
+ AppSetting = appSetting;
+ Environment = environment;
+ Name = name;
+ this.parser = parser;
+ Value = defaultValue;
+ if (!String.IsNullOrEmpty (environment))
+ foreach (string envVar in environment.Split ('|')) {
+ string value = System.Environment.GetEnvironmentVariable (envVar);
+ MaybeParseUpdate (SettingSource.Environment, value);
+ }
+
+ if (!String.IsNullOrEmpty (appSetting))
+ foreach (var appSett in appSetting.Split ('|')) {
+ string value = System.Configuration.ConfigurationManager.AppSettings [appSett];
+ MaybeParseUpdate (SettingSource.AppSettings, value);
+ }
+ }
+
+ public void MaybeParseUpdate (SettingSource settingSource, string value)
+ {
+ if (value == null)
+ return;
+ T result;
+ if (parser (value, out result))
+ MaybeUpdate (settingSource, result);
+ }
+
+ public bool MaybeUpdate (SettingSource source, T value)
+ {
+ if (source < this.source)
+ return false;
+ Value = value;
+ this.source = source;
+ return true;
+ }
+
+ public static implicit operator T (Setting<T> setting)
+ {
+ return setting.Value;
+ }
+
+ SettingSource source = SettingSource.Default;
+ public string Name { get; private set; }
+ public string Prototype { get; private set; }
+ public string Environment { get; private set; }
+ public string AppSetting { get; private set; }
+ public string Description { get; private set; }
+ public T Value { get; private set; }
+ [Obsolete]
+ object ISetting.Value {
+ get { return Value; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Mono.WebServer/Options/Settings/SettingSource.cs b/src/Mono.WebServer/Options/Settings/SettingSource.cs
new file mode 100644
index 0000000..bc6ea9b
--- /dev/null
+++ b/src/Mono.WebServer/Options/Settings/SettingSource.cs
@@ -0,0 +1,38 @@
+//
+// SettingSource.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+namespace Mono.WebServer.Options.Settings {
+ public enum SettingSource
+ {
+ Default,
+ AppSettings,
+ Environment,
+ Xml,
+ CommandLine
+ }
+}
\ No newline at end of file
diff --git a/src/Mono.WebServer/Options/Settings/SettingsCollection.cs b/src/Mono.WebServer/Options/Settings/SettingsCollection.cs
new file mode 100644
index 0000000..e5ff776
--- /dev/null
+++ b/src/Mono.WebServer/Options/Settings/SettingsCollection.cs
@@ -0,0 +1,38 @@
+//
+// SettingsCollection.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System.Collections.ObjectModel;
+
+namespace Mono.WebServer.Options.Settings {
+ public sealed class SettingsCollection : KeyedCollection<string, ISetting> {
+ protected override string GetKeyForItem (ISetting item)
+ {
+ return item.Name;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Mono.WebServer/Options/Settings/StringSetting.cs b/src/Mono.WebServer/Options/Settings/StringSetting.cs
new file mode 100644
index 0000000..06286a5
--- /dev/null
+++ b/src/Mono.WebServer/Options/Settings/StringSetting.cs
@@ -0,0 +1,45 @@
+//
+// StringSetting.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.WebServer.Options.Settings {
+ public class StringSetting : Setting<string>
+ {
+ public StringSetting (string name, string description, string appSetting = null, string environment = null, string defaultValue = null, string prototype = null)
+ : base (name, FakeParse, description, appSetting, environment, defaultValue, prototype)
+ {
+ }
+
+ static bool FakeParse (string value, out string result)
+ {
+ result = value;
+ return !String.IsNullOrEmpty (value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Mono.WebServer/Options/Settings/UInt16Setting.cs b/src/Mono.WebServer/Options/Settings/UInt16Setting.cs
new file mode 100644
index 0000000..fdb4b98
--- /dev/null
+++ b/src/Mono.WebServer/Options/Settings/UInt16Setting.cs
@@ -0,0 +1,39 @@
+//
+// UInt16Setting.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.WebServer.Options.Settings {
+ public class UInt16Setting : Setting<ushort>
+ {
+ public UInt16Setting (string name, string description, string appSetting = null, string environment = null, ushort defaultValue = default(UInt16), string prototype = null)
+ : base (name, UInt16.TryParse, description, appSetting, environment, defaultValue, prototype)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Mono.WebServer/Options/Settings/UInt32Setting.cs b/src/Mono.WebServer/Options/Settings/UInt32Setting.cs
new file mode 100644
index 0000000..55a05de
--- /dev/null
+++ b/src/Mono.WebServer/Options/Settings/UInt32Setting.cs
@@ -0,0 +1,39 @@
+//
+// UInt32Setting.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.WebServer.Options.Settings {
+ public class UInt32Setting : Setting<uint>
+ {
+ public UInt32Setting (string name, string description, string appSetting = null, string environment = null, uint defaultValue = default(UInt32), string prototype = null)
+ : base (name, UInt32.TryParse, description, appSetting, environment, defaultValue, prototype)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Mono.WebServer/Paths.cs b/src/Mono.WebServer/Paths.cs
new file mode 100644
index 0000000..7055f8b
--- /dev/null
+++ b/src/Mono.WebServer/Paths.cs
@@ -0,0 +1,85 @@
+//
+// Paths.cs
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo at ximian.com)
+// Lluis Sanchez Gual (lluis at ximian.com)
+//
+// Copyright (c) Copyright 2002-2007 Novell, Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+using System;
+using System.Web;
+using System.Web.Hosting;
+
+namespace Mono.WebServer
+{
+ public static class Paths
+ {
+ public static void GetPathsFromUri (IApplicationHost appHost, string verb, string uri, out string realUri, out string pathInfo)
+ {
+ // There's a hidden missing feature here... :)
+ realUri = uri; pathInfo = String.Empty;
+ string vpath = HttpRuntime.AppDomainAppVirtualPath;
+ int vpathLen = vpath.Length;
+
+ if (vpath [vpathLen - 1] != '/')
+ vpath += '/';
+
+ if (vpathLen > uri.Length)
+ return;
+
+ uri = uri.Substring (vpathLen);
+ while (uri.Length > 0 && uri [0] == '/')
+ uri = uri.Substring (1);
+
+ int lastSlash = uri.Length;
+
+ for (int dot = uri.LastIndexOf ('.'); dot > 0; dot = uri.LastIndexOf ('.', dot - 1)) {
+ int slash = uri.IndexOf ('/', dot);
+
+ if (slash == -1)
+ slash = lastSlash;
+
+ string partial = uri.Substring (0, slash);
+ lastSlash = slash;
+
+ if (!VirtualPathExists (appHost, verb, partial))
+ continue;
+
+ realUri = vpath + uri.Substring (0, slash);
+ pathInfo = uri.Substring (slash);
+ break;
+ }
+ }
+
+ static bool VirtualPathExists (IApplicationHost appHost, string verb, string uri)
+ {
+ if (appHost.IsHttpHandler (verb, uri))
+ return true;
+
+ VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider;
+ return vpp != null && vpp.FileExists ("/" + uri);
+ }
+ }
+}
diff --git a/src/Mono.WebServer/Platform.cs b/src/Mono.WebServer/Platform.cs
new file mode 100644
index 0000000..fa10ebb
--- /dev/null
+++ b/src/Mono.WebServer/Platform.cs
@@ -0,0 +1,143 @@
+//
+// Platform.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+using Mono.Unix;
+using Mono.Unix.Native;
+using Mono.WebServer.Log;
+
+namespace Mono.WebServer {
+ public static class Platform
+ {
+ public static readonly FinePlatformID Value;
+
+ public static string Name {
+ get { return Value.ToString (); }
+ }
+
+ public static bool IsUnix {
+ get {
+ var platform = Environment.OSVersion.Platform;
+ return platform == PlatformID.Unix || platform == PlatformID.MacOSX || platform == (PlatformID)128;
+ }
+ }
+
+ static Platform() {
+ Value = GetPlatformId();
+ }
+
+ static FinePlatformID GetPlatformId() {
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.Unix:
+ case (PlatformID)128:
+ // Well, there are chances MacOSX is reported as Unix instead of MacOSX.
+ // Instead of platform check, we'll do a feature checks (Mac specific root folders)
+ if (Directory.Exists("/Applications")
+ && Directory.Exists("/System")
+ && Directory.Exists("/Users")
+ && Directory.Exists("/Volumes"))
+ return FinePlatformID.MacOSX;
+ return FinePlatformID.Linux;
+
+ case PlatformID.MacOSX:
+ return FinePlatformID.MacOSX;
+
+ default:
+ return FinePlatformID.Windows;
+ }
+ }
+
+ static uint GetUid (string user)
+ {
+ var info = new UnixUserInfo (user);
+ long uid = info.UserId;
+ if (uid > UInt32.MaxValue || uid <= 0)
+ throw new ArgumentOutOfRangeException ("user", String.Format ("Uid for {0} ({1}) not in range for suid", user, uid));
+ return (uint)uid;
+ }
+
+ static uint GetGid (string group)
+ {
+ var info = new UnixGroupInfo (group);
+ var gid = info.GroupId;
+ if (gid > UInt32.MaxValue || gid <= 0)
+ throw new ArgumentOutOfRangeException ("group", String.Format ("Gid for {0} ({1}) not in range for sgid", group, gid));
+ return (uint)gid;
+ }
+
+ static void SetUser (string user)
+ {
+ Syscall.setuid (GetUid (user));
+ }
+
+ static void SetGroup (string group)
+ {
+ Syscall.setgid (GetGid (group));
+ }
+
+ public static void LogIdentity ()
+ {
+ Logger.Write (LogLevel.Debug, "Uid {0}, euid {1}, gid {2}, egid {3}", Syscall.getuid (), Syscall.geteuid (), Syscall.getgid (), Syscall.getegid ());
+ }
+
+ public static void SetIdentity (uint uid, uint gid)
+ {
+ // TODO: Use platform-specific code
+ if (IsUnix) {
+ Syscall.setgid (gid);
+ Syscall.setuid (uid);
+ LogIdentity ();
+ } else
+ Logger.Write (LogLevel.Warning, "Not dropping privileges");
+ }
+
+ public static void SetIdentity (string user, string group)
+ {
+ // TODO: Use platform-specific code
+ if (IsUnix) {
+ SetGroup (group);
+ SetUser (user);
+ LogIdentity ();
+ } else
+ Logger.Write (LogLevel.Warning, "Not dropping privileges");
+ }
+
+ public static IDisposable Impersonate(string user,string group)
+ {
+ uint uid = GetUid (user);
+ uint gid = GetGid (group);
+ uint euid = Syscall.geteuid ();
+ uint egid = Syscall.getegid ();
+ Syscall.setegid (gid);
+ Syscall.seteuid (uid);
+ return new IdentityToken (euid, egid);
+ }
+ }
+}
diff --git a/src/Mono.WebServer/RequestData.cs b/src/Mono.WebServer/RequestData.cs
new file mode 100644
index 0000000..5f01948
--- /dev/null
+++ b/src/Mono.WebServer/RequestData.cs
@@ -0,0 +1,61 @@
+//
+// Mono.WebServer.RequestData
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo at ximian.com)
+//
+// (C) 2003 Ximian, Inc (http://www.ximian.com)
+// (C) Copyright 2004-2010 Novell, Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System.Text;
+
+namespace Mono.WebServer
+{
+ public class RequestData
+ {
+ public string Verb;
+ public string Path;
+ public string PathInfo;
+ public string QueryString;
+ public string Protocol;
+ public byte [] InputBuffer;
+
+ public RequestData (string verb, string path, string queryString, string protocol)
+ {
+ Verb = verb;
+ Path = path;
+ QueryString = queryString;
+ Protocol = protocol;
+ }
+
+ public override string ToString ()
+ {
+ var sb = new StringBuilder ();
+ sb.AppendFormat ("Verb: {0}\n", Verb);
+ sb.AppendFormat ("Path: {0}\n", Path);
+ sb.AppendFormat ("PathInfo: {0}\n", PathInfo);
+ sb.AppendFormat ("QueryString: {0}\n", QueryString);
+ return sb.ToString ();
+ }
+ }
+}
diff --git a/src/Mono.WebServer/RequestLineException.cs b/src/Mono.WebServer/RequestLineException.cs
new file mode 100644
index 0000000..a05fb45
--- /dev/null
+++ b/src/Mono.WebServer/RequestLineException.cs
@@ -0,0 +1,38 @@
+//
+// Mono.WebServer.RequestLineException
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo at ximian.com)
+//
+// (C) 2003 Ximian, Inc (http://www.ximian.com)
+// (C) Copyright 2004-2010 Novell, Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+using System;
+
+namespace Mono.WebServer
+{
+ class RequestLineException : ApplicationException {
+ public RequestLineException () : base ("Error reading request line")
+ {
+ }
+ }
+}
diff --git a/src/Mono.WebServer/SearchPattern.cs b/src/Mono.WebServer/SearchPattern.cs
new file mode 100644
index 0000000..17d7601
--- /dev/null
+++ b/src/Mono.WebServer/SearchPattern.cs
@@ -0,0 +1,195 @@
+//
+// System.IO.SearchPattern.cs: Filename glob support.
+//
+// Author:
+// Dan Lewis (dihlewis at yahoo.co.uk)
+//
+// (C) 2002
+//
+
+//
+// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+namespace System.IO.Foo {
+
+ // FIXME: there's a complication with this algorithm under windows.
+ // the pattern '*.*' matches all files (i think . matches the extension),
+ // whereas under UNIX it should only match files containing the '.' character.
+
+ class SearchPattern
+ {
+ public SearchPattern (string pattern) : this (pattern, false) { }
+
+ public SearchPattern (string pattern, bool ignore)
+ {
+ SetPattern (pattern, ignore);
+ }
+
+ public void SetPattern (string pattern, bool ignore)
+ {
+ this.ignore = ignore;
+ Compile (pattern);
+ }
+
+ public bool IsMatch (string text)
+ {
+ return Match (ops, text, 0);
+ }
+
+ // private
+
+ Op ops; // the compiled pattern
+ bool ignore; // ignore case
+
+ void Compile (string pattern)
+ {
+ if (pattern == null)
+ throw new ArgumentException ("Invalid search pattern.");
+
+ if (pattern == "*") { // common case
+ ops = new Op (OpCode.True);
+ return;
+ }
+
+ ops = null;
+
+ int ptr = 0;
+ Op last_op = null;
+ while (ptr < pattern.Length) {
+ Op op;
+
+ switch (pattern [ptr]) {
+ case '?':
+ op = new Op (OpCode.AnyChar);
+ ++ ptr;
+ break;
+
+ case '*':
+ op = new Op (OpCode.AnyString);
+ ++ ptr;
+ break;
+
+ default:
+ op = new Op (OpCode.ExactString);
+ int end = pattern.IndexOfAny (WildcardChars, ptr);
+ if (end < 0)
+ end = pattern.Length;
+
+ op.Argument = pattern.Substring (ptr, end - ptr);
+ if (ignore)
+ op.Argument = op.Argument.ToLower ();
+
+ ptr = end;
+ break;
+ }
+
+ if (last_op == null)
+ ops = op;
+ else
+ last_op.Next = op;
+
+ last_op = op;
+ }
+
+ if (last_op == null)
+ ops = new Op (OpCode.End);
+ else
+ last_op.Next = new Op (OpCode.End);
+ }
+
+ bool Match (Op op, string text, int ptr)
+ {
+ while (op != null) {
+ switch (op.Code) {
+ case OpCode.True:
+ return true;
+
+ case OpCode.End:
+ if (ptr == text.Length)
+ return true;
+
+ return false;
+
+ case OpCode.ExactString:
+ int length = op.Argument.Length;
+ if (ptr + length > text.Length)
+ return false;
+
+ string str = text.Substring (ptr, length);
+ if (ignore)
+ str = str.ToLower ();
+
+ if (str != op.Argument)
+ return false;
+
+ ptr += length;
+ break;
+
+ case OpCode.AnyChar:
+ if (++ ptr > text.Length)
+ return false;
+ break;
+
+ case OpCode.AnyString:
+ while (ptr <= text.Length) {
+ if (Match (op.Next, text, ptr))
+ return true;
+
+ ++ ptr;
+ }
+
+ return false;
+ }
+
+ op = op.Next;
+ }
+
+ return true;
+ }
+
+ // private static
+
+ internal static readonly char [] WildcardChars = { '*', '?' };
+
+ class Op {
+ public Op (OpCode code)
+ {
+ Code = code;
+ Argument = null;
+ Next = null;
+ }
+
+ public readonly OpCode Code;
+ public string Argument;
+ public Op Next;
+ }
+
+ enum OpCode {
+ ExactString, // literal
+ AnyChar, // ?
+ AnyString, // *
+ End, // end of pattern
+ True // always succeeds
+ };
+ }
+}
diff --git a/src/Mono.WebServer/UnregisterRequestEventArgs.cs b/src/Mono.WebServer/UnregisterRequestEventArgs.cs
new file mode 100644
index 0000000..fb4ea1b
--- /dev/null
+++ b/src/Mono.WebServer/UnregisterRequestEventArgs.cs
@@ -0,0 +1,44 @@
+//
+// Mono.WebServer.UnregisterRequestEventArgs
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo at ximian.com)
+// Lluis Sanchez Gual (lluis at ximian.com)
+//
+// (C) Copyright 2004-2010 Novell, Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+using System;
+
+namespace Mono.WebServer
+{
+ public class UnregisterRequestEventArgs : EventArgs
+ {
+ public int RequestId { get; private set; }
+
+ public UnregisterRequestEventArgs (int requestId)
+ {
+ RequestId = requestId;
+ }
+ }
+}
diff --git a/src/Mono.WebServer/UnregisterRequestEventHandler.cs b/src/Mono.WebServer/UnregisterRequestEventHandler.cs
new file mode 100644
index 0000000..d3961f7
--- /dev/null
+++ b/src/Mono.WebServer/UnregisterRequestEventHandler.cs
@@ -0,0 +1,33 @@
+//
+// Mono.WebServer.UnregisterRequestEventHandler
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo at ximian.com)
+// Lluis Sanchez Gual (lluis at ximian.com)
+//
+// (C) Copyright 2004-2010 Novell, Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+namespace Mono.WebServer
+{
+ public delegate void UnregisterRequestEventHandler (object sender, UnregisterRequestEventArgs args);
+}
diff --git a/src/Mono.WebServer/VPathToHost.cs b/src/Mono.WebServer/VPathToHost.cs
new file mode 100644
index 0000000..44b0fd1
--- /dev/null
+++ b/src/Mono.WebServer/VPathToHost.cs
@@ -0,0 +1,153 @@
+//
+// VPathToHost.cs
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo at ximian.com)
+// Lluis Sanchez Gual (lluis at ximian.com)
+//
+// Copyright (c) Copyright 2002-2007 Novell, Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+using System;
+using System.Web.Hosting;
+using System.Globalization;
+
+namespace Mono.WebServer
+{
+ public class VPathToHost
+ {
+ public readonly string vhost;
+ public readonly int vport;
+ public readonly string vpath;
+ public string realPath;
+ public readonly bool haveWildcard;
+ public IApplicationHost AppHost;
+ public IRequestBroker RequestBroker;
+
+ public VPathToHost (string vhost, int vport, string vpath, string realPath)
+ {
+ this.vhost = (vhost != null) ? vhost.ToLower (CultureInfo.InvariantCulture) : null;
+ this.vport = vport;
+ this.vpath = vpath;
+ if (String.IsNullOrEmpty (vpath) || vpath [0] != '/')
+ throw new ArgumentException ("Virtual path must begin with '/': " + vpath,
+ "vpath");
+
+ this.realPath = realPath;
+ AppHost = null;
+ if (vhost != null && this.vhost.Length != 0 && this.vhost [0] == '*') {
+ haveWildcard = true;
+ if (this.vhost.Length > 2 && this.vhost [1] == '.')
+ this.vhost = this.vhost.Substring (2);
+ }
+ }
+
+ public bool TryClearHost (IApplicationHost host)
+ {
+ if (AppHost == host) {
+ AppHost = null;
+ return true;
+ }
+
+ return false;
+ }
+
+ public void UnloadHost ()
+ {
+ if (AppHost != null)
+ AppHost.Unload ();
+
+ AppHost = null;
+ }
+
+ public bool Redirect (string path, out string redirect)
+ {
+ redirect = null;
+ if (path.Length == vpath.Length - 1) {
+ redirect = vpath;
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool Match (string vhost, int vport, string vpath)
+ {
+ if (vport != -1 && this.vport != -1 && vport != this.vport)
+ return false;
+
+ if (vpath == null)
+ return false;
+
+ if (vhost != null && this.vhost != null && this.vhost != "*") {
+ int length = this.vhost.Length;
+ string lwrvhost = vhost.ToLower (CultureInfo.InvariantCulture);
+ if (haveWildcard) {
+ if (length > vhost.Length)
+ return false;
+
+ if (length == vhost.Length && this.vhost != lwrvhost)
+ return false;
+
+ if (vhost [vhost.Length - length - 1] != '.')
+ return false;
+
+ if (!lwrvhost.EndsWith (this.vhost))
+ return false;
+
+ } else if (this.vhost != lwrvhost) {
+ return false;
+ }
+ }
+
+ int local = vpath.Length;
+ int vlength = this.vpath.Length;
+ if (vlength > local) {
+ // Check for /xxx requests to be redirected to /xxx/
+ if (this.vpath [vlength - 1] != '/')
+ return false;
+
+ return (vlength - 1 == local && this.vpath.Substring (0, vlength - 1) == vpath);
+ }
+
+ return (vpath.StartsWith (this.vpath));
+ }
+
+ public void CreateHost (ApplicationServer server, WebSource webSource)
+ {
+ string v = vpath;
+ if (v != "/" && v.EndsWith ("/")) {
+ v = v.Substring (0, v.Length - 1);
+ }
+
+ AppHost = ApplicationHost.CreateApplicationHost (webSource.GetApplicationHostType(), v, realPath) as IApplicationHost;
+ AppHost.Server = server;
+
+ if (!server.SingleApplication) {
+ // Link the host in the application domain with a request broker in the main domain
+ RequestBroker = webSource.CreateRequestBroker ();
+ AppHost.RequestBroker = RequestBroker;
+ }
+ }
+ }
+}
diff --git a/src/Mono.WebServer/Version.cs b/src/Mono.WebServer/Version.cs
new file mode 100644
index 0000000..ba27e53
--- /dev/null
+++ b/src/Mono.WebServer/Version.cs
@@ -0,0 +1,62 @@
+//
+// Version.cs
+//
+// Author:
+// Leonardo Taglialegne <leonardo.taglialegne at gmail.com>
+//
+// Copyright (c) 2013 Leonardo Taglialegne.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+using System.Reflection;
+
+namespace Mono.WebServer {
+ public static class Version
+ {
+ public static void Show ()
+ {
+ Assembly assembly = Assembly.GetExecutingAssembly ();
+ string version = assembly.GetName ().Version.ToString ();
+
+ // string title = GetAttribute<AssemblyTitleAttribute> (a => a.Title);
+ string copyright = GetAttribute<AssemblyCopyrightAttribute> (a => a.Copyright);
+ string description = GetAttribute<AssemblyDescriptionAttribute> (a => a.Description);
+
+ Console.WriteLine ("{0} {1}\n(c) {2}\n{3}",
+ Path.GetFileName (assembly.Location), version,
+ copyright, description);
+ }
+
+ static string GetAttribute<T> (Func<T, string> func) where T : class
+ {
+ Assembly assembly = Assembly.GetExecutingAssembly ();
+ var attributes = assembly.GetCustomAttributes (typeof (T), false);
+ if (attributes.Length == 0)
+ return String.Empty;
+ var att = attributes [0] as T;
+ if (att == null)
+ return String.Empty;
+ return func (att);
+ }
+ }
+}
diff --git a/src/Mono.WebServer/WebSource.cs b/src/Mono.WebServer/WebSource.cs
new file mode 100644
index 0000000..ef7698c
--- /dev/null
+++ b/src/Mono.WebServer/WebSource.cs
@@ -0,0 +1,55 @@
+//
+// Mono.WebServer.WebSource
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo at ximian.com)
+// Lluis Sanchez Gual (lluis at ximian.com)
+//
+// Documentation:
+// Brian Nickel
+//
+// (C) Copyright 2004-2010 Novell, Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Net.Sockets;
+
+namespace Mono.WebServer
+{
+ public abstract class WebSource : IDisposable
+ {
+ public abstract Socket CreateSocket ();
+ public abstract Worker CreateWorker (Socket client, ApplicationServer server);
+ public abstract Type GetApplicationHostType ();
+ public abstract IRequestBroker CreateRequestBroker ();
+
+ public void Dispose ()
+ {
+ Dispose (true);
+ GC.SuppressFinalize (this);
+ }
+
+ protected virtual void Dispose (bool disposing)
+ {
+ }
+ }
+}
diff --git a/src/Mono.WebServer/WebTrace.cs b/src/Mono.WebServer/WebTrace.cs
new file mode 100644
index 0000000..5303431
--- /dev/null
+++ b/src/Mono.WebServer/WebTrace.cs
@@ -0,0 +1,137 @@
+//
+// ApplicationServer.cs
+//
+// Authors:
+// Marek Habersack (mhabersack at novell.com)
+//
+// Copyright (c) Copyright 2007 Novell, Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Diagnostics;
+using System.Reflection;
+
+namespace Mono.WebServer
+{
+ public sealed class WebTrace
+ {
+ static string GetMethodName (StackFrame sf)
+ {
+ MethodBase mi = sf.GetMethod ();
+
+ return mi != null ? String.Format ("{0}.{1}(): ", mi.ReflectedType, mi.Name) : null;
+ }
+
+ static string GetExtraInfo (StackFrame sf = null)
+ {
+ string threadid = String.Format ("thread_id: {0}", Thread.CurrentThread.ManagedThreadId.ToString ("x"));
+ string domainid = String.Format ("appdomain_id: {0}", AppDomain.CurrentDomain.Id.ToString ("x"));
+ string filepath = sf == null ? null : sf.GetFileName ();
+ int lineNumber = sf == null ? -1 : sf.GetFileLineNumber ();
+ string format = String.IsNullOrEmpty (filepath) ? " [{0}, {1}]" : " [{0}, {1}, in {2}:{3}]";
+ return String.Format (format, domainid, threadid, filepath, lineNumber);
+ }
+
+ static void Enter (string format, StackFrame sf, params object[] parms)
+ {
+ var sb = new StringBuilder ("Enter: ");
+
+ string methodName = GetMethodName (sf);
+ if (methodName != null)
+ sb.Append (methodName);
+ if (format != null)
+ sb.AppendFormat (format, parms);
+ sb.Append (GetExtraInfo (sf));
+
+ Trace.WriteLine (sb.ToString ());
+ Trace.Indent ();
+ }
+
+ [Conditional ("WEBTRACE")]
+ public static void Enter (string format, params object[] parms)
+ {
+ Enter (format, new StackFrame (1), parms);
+ }
+
+ [Conditional ("WEBTRACE")]
+ public static void Enter ()
+ {
+ Enter (null, new StackFrame (1), null);
+ }
+
+ static void Leave (string format, StackFrame sf, params object[] parms)
+ {
+ var sb = new StringBuilder ("Leave: ");
+
+ string methodName = GetMethodName (sf);
+ if (methodName != null)
+ sb.Append (methodName);
+ if (format != null)
+ sb.AppendFormat (format, parms);
+ sb.Append (GetExtraInfo (sf));
+
+ Trace.Unindent ();
+ Trace.WriteLine (sb.ToString ());
+ }
+
+ [Conditional ("WEBTRACE")]
+ public static void Leave (string format, params object[] parms)
+ {
+ Leave (format, new StackFrame (1), parms);
+ }
+
+ [Conditional ("WEBTRACE")]
+ public static void Leave ()
+ {
+ Leave (null, new StackFrame (1), null);
+ }
+
+ [Conditional ("WEBTRACE")]
+ public static void WriteLine (string format, params object[] parms)
+ {
+ if (format == null)
+ return;
+
+ var sb = new StringBuilder ();
+ sb.AppendFormat (format, parms);
+ sb.Append (GetExtraInfo ());
+ Trace.WriteLine (sb.ToString ());
+ }
+
+ [Conditional ("WEBTRACE")]
+ public static void WriteLineIf (bool cond, string format, params object[] parms)
+ {
+ if (!cond)
+ return;
+
+ if (format == null)
+ return;
+
+ var sb = new StringBuilder ();
+ sb.AppendFormat (format, parms);
+ sb.Append (GetExtraInfo ());
+ Trace.WriteLine (sb.ToString ());
+ }
+ }
+}
diff --git a/src/Mono.WebServer/Worker.cs b/src/Mono.WebServer/Worker.cs
new file mode 100644
index 0000000..3487dc8
--- /dev/null
+++ b/src/Mono.WebServer/Worker.cs
@@ -0,0 +1,62 @@
+//
+// Mono.WebServer.Worker
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo at ximian.com)
+// Lluis Sanchez Gual (lluis at ximian.com)
+//
+// Documentation:
+// Brian Nickel
+//
+// (C) Copyright 2004-2010 Novell, Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+namespace Mono.WebServer
+{
+ public abstract class Worker
+ {
+ public virtual bool IsAsync {
+ get { return false; }
+ }
+
+ public virtual void SetReuseCount (int reuses)
+ {
+ }
+
+ public virtual int GetRemainingReuses ()
+ {
+ return 0;
+ }
+
+ public abstract void Run (object state);
+
+ public abstract int Read (byte [] buffer, int position, int size);
+
+ public abstract void Write (byte [] buffer, int position, int size);
+
+ public abstract void Close ();
+
+ public abstract void Flush ();
+
+ public abstract bool IsConnected ();
+ }
+}
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-mono/packages/xsp.git
More information about the Pkg-mono-svn-commits
mailing list