[Mono-list] [Fwd] Miguel's hack to make gnumeric + mono ASP.NET work together

Gonzalo Paniagua Javier gonzalo@gnome-db.org
Thu, 25 Jul 2002 01:59:42 +0200


Hi, I'm sending this on behalf of Miguel:
---------------------
[I did the translation, so blame errors on me :)]

I have just finished this in the plane. It's not something beautiful,
it's just a 2 minutes hack. You gotta initizlize Mono on gnumeric,
change the Makefiles and then add "mono.c" to the list of files to
compile.

This loads gnumeric.dll at startup. This can do almost everything. In
this case I copied Gonzalo's web server and make some tweaks so as
gnumeric becomes a 'web server'.

I mean, when i launch gnumeric i can connect to it using a browser

	http://localhost:8000/
		Status of gnumeric sheets.

	http://localhost:8000/Gnumeric.aspx
		Enter data to gnumeric via web.

	http://localhost:8000/gnumeric/EXPRESION
		Evaluates EXPRESION inside gnumeric and returns back
		the result to the web page.

My gnumeric.dll is basically:

	mcs /r:System.Web server.cs gnumeric.cs /target:library /out:gnumeric.dll

I would send  a diff, but right now I'm in a hote using a modem.

Cheers,
Miguel

--=-qIfMv7hquFFSE79UFo9H
Content-Disposition: attachment; filename="mono.c"
Content-Type: text/x-c; name="mono.c"; charset=ISO-8859-1
Content-Transfer-Encoding: 8bit

#include <mono/jit/jit.h>
#include <gnumeric-config.h>
#include <glib.h>
#include "gnumeric.h"
#include "workbook.h"
#include "sheet.h"
#include "mono.h"
#include "cell.h"
#include "rendered-value.h"
#include "str.h"

MonoDomain *domain;
MonoAssembly *assembly;

static void
Gnumeric_SetCell (int col, int row, MonoString *s)
{
	Sheet *sheet = workbook_sheet_by_index (current_workbook, 0);
	Cell *cell = sheet_cell_get (sheet, col, row);
	
	sheet_cell_set_text (cell, mono_string_to_utf8 (s));
}

static MonoString *
Gnumeric_GetStatus ()
{
	GString *str = g_string_new ("");
	GList *l;
	
	g_string_append (str, "Workbook: ");
	g_string_append (str, current_workbook->filename);
	g_string_append (str, "<p><ul>");
	
	for (l = workbook_sheets (current_workbook); l != NULL; l = l->next){
		Sheet *s = (Sheet *) l->data;
		
		g_string_append_printf (
			str, "<li>%s<ul><li>%s<li>%s</ul>", s->name_quoted,
			s->modified ? "modified" : "pristine",
			s->display_formulas ? "diplaying formulas" : "rendering formula values");
	}
	g_string_append (str, "</ul>");

	return mono_string_new (domain, str->str);
}

volatile int col, row;
volatile char *task;
volatile char *result;
int fd [2];
GIOChannel *pfd;

static MonoString *
Gnumeric_Eval (MonoString *ref)
{
	char c = 0;
	task = mono_string_to_utf8 (ref);
	col = 30;
	row = 10;
	write (fd [1], &c, 1);

	while (result == NULL){
		g_thread_yield ();
		sleep (1);
	}

	return mono_string_new (domain, result);
}

static void
Gnumeric_SetText (int c, int r, MonoString *ref)
{
	char cd = 0;
	task = mono_string_to_utf8 (ref);
	col = c;
	row = r;
	
	write (fd [1], &cd, 1);
}

gboolean
compute (GIOChannel *source, GIOCondition a, gpointer data)
{
	Sheet *sheet = workbook_sheet_by_index (current_workbook, 0);
	Cell *cell = sheet_cell_fetch (sheet, col, row);
	char c;

	read (fd [0], &c, 1);
	sheet_cell_set_text (cell, task);
	workbook_recalc_all (current_workbook);
	cell_render_value (cell, TRUE);
	result = cell->rendered_value->rendered_text->str;

	return TRUE;
}

int
init_mono ()
{
	const char *file;
	char *argv [] = { NULL };
	int retval;

	domain = mono_jit_init ("gnumeric.dll");
	
	mono_add_internal_call ("Gnumeric::SetCell", Gnumeric_SetCell);
	mono_add_internal_call ("Gnumeric::GetStatus", Gnumeric_GetStatus);
	mono_add_internal_call ("Gnumeric::Eval", Gnumeric_Eval);
	mono_add_internal_call ("Gnumeric::SetText", Gnumeric_SetText);
	
	assembly = mono_domain_assembly_open (domain, "gnumeric.dll");
	if (!assembly)
		return 2;
	/*
	 * mono_jit_exec() will run the Main() method in the assembly
	 * and return the value.
	 */
	
	pipe (fd);
	pfd = g_io_channel_unix_new (fd [0]);
	g_io_add_watch (pfd, G_IO_IN, compute, NULL);
	
	retval = mono_jit_exec (domain, assembly, 0, argv);
				     
	return 0;
}

--=-qIfMv7hquFFSE79UFo9H
Content-Disposition: attachment; filename="mono.h"
Content-Type: text/x-c-header; name="mono.h"; charset=ISO-8859-1
Content-Transfer-Encoding: 8bit


extern Workbook *current_workbook;


--=-qIfMv7hquFFSE79UFo9H
Content-Disposition: attachment; filename="Gnumeric.aspx"
Content-Type: text/plain; name="Gnumeric.aspx"; charset=ISO-8859-1
Content-Transfer-Encoding: 8bit

<%@ Page Language="C#" %>
<%@ import namespace="Gnumeric" %>
<script runat=server>
//<reference dll="gnumeric"/>
	int getnum (string s)
	{
		try {
			return Int32.Parse (s);
		} catch {
			return 1;
		}
	}

	void Clicked (object o, EventArgs e)
	{
		Gnumeric.SetText (getnum (col.Text), getnum (row.Text), value.Text);
		status.Text = getnum (col.Text).ToString () + ":" +
			      getnum (row.Text).ToString () + ":" +
			      value.Text;
	}
</script>

<html>
<body>
	<p>
	<p>
	<table>
	  <tr>
	    <td>	<img src="mono-logo-win.png"></td>
	<td>
	<h3>Welcome to Miggy's Enterprise Web Access to Gnumeric</h3>

	<form runat="server">
		<table>
		  <tr>
		    <td>
 			Column:
		    </td>
		    <td> <asp:TextBox id="col" Text="1" runat="server" maxlength=40 /></td>
		  </tr>
	 	  <tr>
		    <td>Row:</td>
		    <td><asp:TextBox id="row" Text="1" runat="server" maxlength=40 /></td>
		  </tr>
	          <tr><td>Value:</td>
		    <td><asp:TextBox id="value" Text="hello" runat="server" maxlength=40 /></td>
	          </tr>
		</table>
		<asp:label id="status" runat="server"/>
		<p>
		<asp:Button id="btn"
		     Text="Set Value"
		     OnClick="Clicked"
		     runat="server"/>
	
	</form>
	</td>
	</tr>
	</table>

</body>
</html>


--=-qIfMv7hquFFSE79UFo9H
Content-Disposition: attachment; filename="gnumeric.cs"
Content-Type: text/plain; name="gnumeric.cs"; charset=ISO-8859-1
Content-Transfer-Encoding: 8bit

using System;
using System.Runtime.CompilerServices;

class Gnumeric {
	[MethodImplAttribute(MethodImplOptions.InternalCall)]
	public extern static void SetCell (int col, int row, string value);

	[MethodImplAttribute (MethodImplOptions.InternalCall)]
	public extern static string GetStatus ();

	[MethodImplAttribute (MethodImplOptions.InternalCall)]
	public extern static string Eval (string s);

	[MethodImplAttribute (MethodImplOptions.InternalCall)]
	public extern static string SetText (int col, int row, string s);
}


--=-qIfMv7hquFFSE79UFo9H
Content-Disposition: attachment; filename="server.cs"
Content-Type: text/plain; name="server.cs"; charset=ISO-8859-1
Content-Transfer-Encoding: 8bit

namespace Mono.ASP {

using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Reflection;
using System.Threading;
using System.Web;
using System.Web.Configuration;
using System.Web.Hosting;
using System.Web.UI;

class MyCapabilities : HttpBrowserCapabilities
{
	private Hashtable capabilities;

	public MyCapabilities ()
	{
		capabilities = new Hashtable ();
	}
	
	public void Add (string key, string value)
	{
		capabilities.Add (key, value);
	}

	public override string this [string value]
	{
		get { return capabilities [value] as string; }
	}
}

class CacheData
{
	private AppDomain domain;
	private string dllName;
	private string className;
	private string fileName;
	private DateTime writeTime;
	private bool unloaded;

	public CacheData (string fileName, string dllName, string className, DateTime writeTime)
	{
		DateTime dt = DateTime.Now;
		this.dllName = dllName;
		domain = AppDomain.CreateDomain (dllName);
		this.fileName = fileName;
		this.dllName = dllName;
		this.className = className;
		this.writeTime = writeTime;
	}

	public bool OlderThan (DateTime date)
	{
		if (unloaded)
			throw new ApplicationException ("This domain is unloaded: " + dllName);

		return (writeTime < date);
	}

	public object CreateInstance ()
	{
		if (unloaded)
			throw new ApplicationException ("This domain is unloaded: " + dllName);

		return domain.CreateInstanceFromAndUnwrap (dllName, className);
	}

	public void Unload ()
	{
		if (unloaded)
			throw new ApplicationException ("This domain is unloaded: " + dllName);

		unloaded = true;
		AppDomain.Unload (domain);
		domain = null;
	}
}

class PageFactory
{
	public class PageBuilder
	{
		private StringBuilder cscOptions;
		private string fileName;
		private string csFileName;
		private string className;
		public static char dirSeparator = Path.DirectorySeparatorChar;
		private static Hashtable cachedData = new Hashtable ();
		private static Random rnd_file = new Random ();

		public static bool CheckDate (string fileName)
		{
			CacheData cached = cachedData [fileName] as CacheData;
			DateTime fileWriteTime = File.GetLastWriteTime (fileName);

			if (cached != null && cached.OlderThan (fileWriteTime)) {
				cachedData.Remove (fileName);
				cached.Unload ();
				cached = null;
				return false;
			}

			return true;
		}

		private PageBuilder ()
		{
		}

		public PageBuilder (string fileName)
		{
			this.fileName = fileName;
			csFileName = "xsp_" + Path.GetFileName (fileName).Replace (".aspx", ".cs");

			cscOptions = new StringBuilder ();
			cscOptions.Append ("--target library ");
			cscOptions.Append ("-L . ");
			AddReference ("corlib");
			AddReference ("System");
			AddReference ("System.Data");
			AddReference (Server.SystemWeb);
			AddReference (Server.SystemDrawing);
			AddReference ("gnumeric");
		}

		public Page Build ()
		{
			CacheData cached = cachedData [fileName] as CacheData;
			string dll;
			DateTime fileWriteTime = File.GetLastWriteTime (fileName);

			if (cached != null && cached.OlderThan (fileWriteTime)) {
				cachedData.Remove (fileName);
				cached.Unload ();
				cached = null;
			}
			
			if (cached == null) {
				if (Xsp (fileName, csFileName) == false){
					Console.WriteLine ("Error running xsp. " + 
							   "Take a look at the output file.");
					return null;
				}

				StreamReader st_file = new StreamReader (File.OpenRead ("output" +
											dirSeparator +
											csFileName));
				
				StringReader file_content = new StringReader (st_file.ReadToEnd ());
				st_file.Close ();
				if (GetBuildOptions (file_content) == false)
					return null;

				dll = "output" + dirSeparator;
				dll += rnd_file.Next () + Path.GetFileName (fileName).Replace (".aspx", ".dll");
				if (Compile (csFileName, dll) == true){
					cached = new CacheData (fileName,
								dll,
								"ASP." + className,
								fileWriteTime);
					cachedData.Add (fileName, cached);
				}
			}

			if (cached == null)
				return null;

			return GetInstance (cached);
		}

		private static bool Xsp (string fileName, string csFileName)
		{
			return RunProcess ("mono", 
					   "xsp.exe " + fileName, 
					   GeneratedXspFileName (fileName),
					   "output" + dirSeparator + "xsp_" + Path.GetFileName (fileName) + 
					   ".sh");
		}

		private static bool RunProcess (string exe, string arguments, string output_file, string script_file)
		{
			Console.WriteLine ("{0} {1}", exe, arguments);
			Console.WriteLine ("Output goes to {0}", output_file);
			Console.WriteLine ("Script file is {0}", script_file);
			Process proc = new Process ();

			proc.StartInfo.FileName = "redirector.sh";
			proc.StartInfo.Arguments = exe + " " + output_file + " " + arguments;
			proc.Start ();
			proc.WaitForExit ();
			int result = proc.ExitCode;
			proc.Close ();

			StreamWriter bat_output = new StreamWriter (File.Create (script_file));
			bat_output.Write ("redirector.sh" + " " + exe + " " + output_file + " " + arguments);
			bat_output.Close ();
			return (result == 0);
		}

		private bool GetBuildOptions (StringReader genCode)
		{
			string line;
			string dll;

			while ((line = genCode.ReadLine ()) != String.Empty) {
				if (line.StartsWith ("//<class ")){
					className = GetAttributeValue (line, "name");
				} else if (line.StartsWith ("//<compileandreference ")) {
					string src = GetAttributeValue (line, "src");
					dll = src.Replace (".cs", ".dll"); //FIXME
					//File.Delete (dll);
					if (Compile (src, dll) == false){
						Console.WriteLine ("Error compiling {0}. See the output file.", src);
						return false;
					}
					AddReference (dll.Replace (".dll", ""));
				} else if (line.StartsWith ("//<reference ")) {
					dll = GetAttributeValue (line, "dll");
					AddReference (dll);
				} else if (line.StartsWith ("//<compileroptions ")) {
					string options = GetAttributeValue (line, "options");
					cscOptions.Append (" " + options + " ");
				} else {
					Console.WriteLine ("This is the build option line i get:\n" + line);
					return false;
				}
			}

			return true;
		}

		private void AddReference (string reference)
		{
			string arg;

			arg = String.Format ("-r {0} ", reference.Replace (".dll", ""));
			cscOptions.Append (arg);
		}
		
		private string GetAttributeValue (string line, string att)
		{
			string att_start = att + "=\"";
			int begin = line.IndexOf (att_start);
			int end = line.Substring (begin + att_start.Length).IndexOf ('"');
			if (begin == -1 || end == -1)
				throw new ApplicationException ("Error in reference option:\n" + line);

			return line.Substring (begin + att_start.Length, end);
		}
		
		private static Page GetInstance (CacheData cached)
		{
			return cached.CreateInstance () as Page;
		}

		private bool Compile (string csName, string dllName)
		{
			cscOptions.AppendFormat ("-o {0} ", dllName);
			cscOptions.Append ("output" + dirSeparator + csName);

			string cmdline = cscOptions.ToString ();
			string noext = csName.Replace (".cs", "");
			string output_file = "output" + dirSeparator + "output_from_compilation_" + noext + ".txt";
			string bat_file = "output" + dirSeparator + "last_compilation_" + noext + ".bat";
			return RunProcess ("mcs", cmdline, output_file, bat_file);
		}
	}

	private static Hashtable loadedPages = new Hashtable ();


	public static string CompilationOutputFileName (string fileName)
	{
		string name = "xsp_" + Path.GetFileName (fileName).Replace (".aspx", ".txt");
		return "output" + PageBuilder.dirSeparator + "output_from_compilation_" + name;
	}

	public static string GeneratedXspFileName (string fileName)
	{
		string name = Path.GetFileName (fileName).Replace (".aspx", ".cs");
		return "output" + PageBuilder.dirSeparator + "xsp_" + name;
	}

	private PageFactory ()
	{
	}

	public static Page GetPage (string fileName, string query_options)
	{
		HttpRequest request = new HttpRequest (fileName, "http://127.0.0.1/" + fileName, 
						       query_options);

		string view_state = request.QueryString ["__VIEWSTATE"];
		if (view_state != null && loadedPages.ContainsValue (view_state)){
			Page p = null;
			foreach (Page _p in loadedPages.Keys){
				if (view_state == loadedPages [_p] as string){
					if (PageBuilder.CheckDate (fileName)) {
						p = _p;
					} else {
						loadedPages.Remove (_p);
					}
					break;
				}
			}

			if (p != null)
				return p;
		}

		PageBuilder builder = new PageBuilder (fileName);
		Page page = builder.Build ();
		if (page != null)
			loadedPages.Add (page, null);

		return page;
	}

	public static void UpdateHash (Page page, string new_state)
	{
		if (!(loadedPages.ContainsKey (page)))
			return;

		loadedPages [page] = new_state;
	}

}

class HttpHelpers
{
	public static void SendStatus (TextWriter writer, int code, string message)
	{
		writer.Write ("HTTP/1.0 " + code + " " + message + "\r\n");
	}

	public static void SendHeader (TextWriter writer, string title, string data)
	{
		writer.Write (title + ": " + data + "\r\n");
	}
	
	public static void RenderErrorPage (TextWriter writer, int code, string description, string message)
	{
		SendStatus (writer, code, message);
		SendHeader (writer, "Content-Type", "text/html");
		string content = String.Format ("<html>\n<title>Error {0}: {1}</title>\n<body>" + 
						"<h2>Error {0}: {1}</h2><p>{2}\n</body>\n</html>\n",
						code, description, message);
		SendHeader (writer, "Content-Length", content.Length.ToString ());
		writer.Write ("\r\n");
		writer.Write (content);
	}

	public static void WriteHTML (TextWriter writer, string source)
	{
		writer.Write ("HTTP/1.0 200 OK\r\n");
		SendHeader (writer, "Content-Type", "text/html");
		SendHeader (writer, "Content-Length", source.Length.ToString ());
		writer.Write ("\r\n");
		writer.Write (source);
	}
	
	private static void WriteFormattedSource (TextWriter writer, string source)
	{
		StringReader reader = new StringReader (source);
		string line;
		int lineno = 1;
		while ((line = reader.ReadLine ()) != null){
			writer.WriteLine ("line " + lineno + ": " + HttpUtility.HtmlEncode (line));
			lineno++;
		}
		reader.Close ();
	}

	public static void SendDebugPage (TextWriter writer, string fileName)
	{
		string xspOutput = PageFactory.GeneratedXspFileName (fileName);
		if (!File.Exists (xspOutput)) {
			RenderErrorPage (writer, 500, "Internal Server Error", "Couldn't run xsp.exe");
			return;
		}
		
		string compilationOutput = PageFactory.CompilationOutputFileName (fileName);
		Console.WriteLine (compilationOutput);
		if (!File.Exists (compilationOutput)) {
			RenderErrorPage (writer, 500, "Internal Server Error", "xsp.exe failed to generate code");
			return;
		}

		StreamReader csReader = new StreamReader (File.Open (xspOutput, FileMode.Open));
		string csContent = csReader.ReadToEnd ();
		csReader.Close ();

		StreamReader compilationReader = new StreamReader (File.Open (compilationOutput, FileMode.Open));
		string compilationContent = compilationReader.ReadToEnd ();
		compilationReader.Close ();

		StringWriter output = new StringWriter ();
		output.WriteLine ("<html>\n<title>Compilation failed</title>\n<body>");
		output.WriteLine ("<h2>Compilation failed</h2>");
		output.WriteLine ("The output from the compiler is:<p>");
		output.WriteLine ("<table summary=\"\" bgcolor=\"#FFFFCC\">\n<tr>\n<td>");
		output.WriteLine ("<code><pre>");
		output.WriteLine (HttpUtility.HtmlEncode (compilationContent));
		output.WriteLine ("</pre></code>\n</td></tr></table><p>");
		output.WriteLine ("<b>Compilation source code:</b><p>\n");
		output.WriteLine ("<table summary=\"\" bgcolor=\"#FFFFCC\"><tr><td><code><pre>");
		WriteFormattedSource (output, csContent);
		output.WriteLine ("</pre></code></td></tr></table>");
		output.WriteLine ("</body>\n</html>");

		SendStatus (writer, 200, "OK");
		SendHeader (writer, "Content-Type", "text/html");
		string content = output.ToString ();
		output.Close ();
		SendHeader (writer, "Content-Length", content.Length.ToString ());
		writer.Write ("\r\n");
		writer.Write (content);
	}
}

class MyWorkerRequest
{
	private string fileName;
	private string fileOnDisk;
	private TextReader input;
	private TextWriter output;
	private StreamWriter stream_output;
	private TextWriter outputBuffer;
	private HttpResponse response;

	private string method;
	private string query;
	private string protocol;
	private string query_options = "";
	private int post_size;
	private MyCapabilities headers;
	private static Hashtable mimeHash;
	private static char dirSeparator = Path.DirectorySeparatorChar;

	static MyWorkerRequest ()
	{
		mimeHash = new Hashtable (new CaseInsensitiveHashCodeProvider (),
					  new CaseInsensitiveComparer ());
		mimeHash.Add ("jpg", "image/jpeg");
		mimeHash.Add ("png", "image/png");
		mimeHash.Add ("css", "text/css");
		mimeHash.Add ("aspx", "text/html");
		mimeHash.Add ("htm", "text/html");
		mimeHash.Add ("html", "text/html");
		mimeHash.Add ("txt", "text/plain");
		mimeHash.Add ("xml", "text/xml");
	}
	
	private MyWorkerRequest ()
	{
	}

	public MyWorkerRequest (TextReader input, TextWriter output, StreamWriter stream_output)
	{
		if (input == null || output == null)
			throw new ArgumentNullException ();

		this.input = input;
		this.output = output;
		this.stream_output = stream_output;
		outputBuffer = new StringWriter ();
	}

	public void RenderErrorPage (int code, string message, string description)
	{
		HttpHelpers.RenderErrorPage (output, code, message, description);
	}

	public void SendGnumericOutput (TextWriter output)
	{
		HttpHelpers.WriteHTML (output, "<html><title>Gnumeric</title>"+
				       Gnumeric.GetStatus () +  
				       "</html>");
		
	}

	public void SendContent (TextWriter output, string text)
	{
		string s = Gnumeric.Eval (text);
		
		HttpHelpers.WriteHTML (output, "<html><title>Gnumeric content</title>" +
				       "The expression: " + text + " is:<p>" +
				       s +
				       "</html>");
	}
	
	public void ProcessRequest ()
	{
		if (GetRequestData () == false)
			return;

		if (fileName == ""){
			SendGnumericOutput (output);
			return;
		}

		if (fileName.StartsWith ("gnumeric/")){
			SendContent (output, fileName.Substring (9).Replace ("%3A", ":"));
			return;
		}
		
		if (fileName.IndexOf ("..") != -1){
			RenderErrorPage (400, "Bad request", ".. not allowed in request");
			return;
		}

		fileOnDisk = fileName;
		if (dirSeparator != '/')
			fileOnDisk.Replace ('/', dirSeparator);

		bool is_dir = Directory.Exists (fileOnDisk);
		if (fileOnDisk == String.Empty || is_dir) {
			string dir = is_dir ? Path.GetDirectoryName (fileOnDisk) : "";
			
			if (File.Exists ("index.aspx"))
				fileOnDisk = dir + dirSeparator + "index.aspx";
			else if (File.Exists ("index.html"))
				fileOnDisk = dir + dirSeparator + "index.html";
			
			if (Path.IsPathRooted (fileOnDisk))
				fileOnDisk = fileOnDisk.Substring (1);
				
			fileName = fileOnDisk;
		}

		if (!File.Exists (fileOnDisk)){
			RenderErrorPage (404, "Not Found", "File '" + fileName + "' not found.");
			return;
		}

		if (!fileName.EndsWith (".aspx")){
			ProcessRequestNonASPX ();
			return;
		}

		Page page = PageFactory.GetPage (fileOnDisk, query_options);
		if (page == null){
			HttpHelpers.SendDebugPage (output, fileOnDisk);
			return;
		}
		RenderPage (page);
		string new_view_state = page.GetViewStateString ();
		PageFactory.UpdateHash (page, new_view_state);
	}
	
	void ProcessRequestNonASPX ()
	{
		FileInfo fi = new FileInfo (fileOnDisk);

		stream_output.Write ("HTTP/1.0 200 OK\r\n" +
				     "Host 127.0.0.1\r\n" +
				     "ContentType: ");
		stream_output.Write (GetContentType ());
		stream_output.Write ("\r\nContent-Length: ");
		stream_output.Write (fi.Length.ToString ());

		Stream bases = stream_output.BaseStream;
		Stream fileInput = File.Open (fileOnDisk, FileMode.Open); //FIXME
		byte [] fileContent = new byte [8192];
		int count = fileContent.Length;
		while ((count = fileInput.Read (fileContent, 0, count)) != 0) {
			bases.Write (fileContent, 0, count);
		}

		fileInput.Close ();
	}
	
	private void GetRequestMethod ()
	{
		string req = input.ReadLine ();
		if (req == null)
			throw new ApplicationException ("Void request.");

		if (0 == String.Compare ("GET ", req.Substring (0, 4), true))
			method = "GET";
		else if (0 == String.Compare ("POST ", req.Substring (0, 5), true))
			method = "POST";
		else
			throw new InvalidOperationException ("Unrecognized method in query: " + req);

		req = req.TrimEnd ();
		int idx = req.IndexOf (' ') + 1;
		if (idx >= req.Length)
			throw new ApplicationException ("What do you want?");

		string page_protocol = req.Substring (idx);
		int idx2 = page_protocol.IndexOf (' ');
		if (idx2 == -1)
			idx2 = page_protocol.Length;
		
		query = page_protocol.Substring (0, idx2);
		protocol = page_protocol.Substring (idx2);
	}

	private void GetCapabilities ()
	{
		headers = new MyCapabilities ();
		if (protocol == "")
			return;
		
		string line;
		int idx;
		while ((line = input.ReadLine ()) != "") {
			if (line == null){
				headers.Add ("Accept", "*/*");

				headers.Add ("Referer", "http://127.0.0.1/");
				headers.Add ("Accept-Language", "es");
				headers.Add ("Accept-Encoding", "gzip, deflate");
				headers.Add ("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; " + 
							   "Windows NT 5.0; .NET CLR 1.0.3705)");
				headers.Add ("Host", "127.0.0.1");
				return;
			}

			idx = line.IndexOf (':');
			if (idx == -1 || idx == line.Length - 1){
				Console.Error.WriteLine ("idx: {0} Ignoring request header: {1}", idx, line);
				continue;
			}
			string key = line.Substring (0, idx);
			string value = line.Substring (idx + 1);
			headers.Add (key, value);
			if (key == "Content-Length")
				post_size = Int32.Parse (value.Trim ());
		}
	}
	
	private void GetQueryOptions ()
	{
		if (method == "POST") {
			char [] line = new char [post_size];
			input.Read (line, 0, post_size);
			query_options = new string (line);
		}
		
		if (query_options == null)
			query_options = "";
	}
	
	private bool GetRequestData ()
	{
		GetRequestMethod ();
		GetCapabilities ();
		GetQueryOptions ();

		if (query [0] == '/')
			query = query.Substring (1);

		int end = query.IndexOf (' ');
		string target;
		if (end != -1)
			target = query.Substring (0, end);
		else
			target = query;

		int qmark = target.IndexOf ('?');
		if (qmark == -1){
			fileName = target;
			return true;
		}
		
		if (qmark == 0){
			RenderErrorPage (400, "Bad Request", "Malformed query string.");
			return false;
		}

		fileName = target.Substring (0, qmark);
		if (query_options == "")
			query_options = target.Substring (qmark + 1);
		return true;
	}
	
	private void RenderPage (Page page)
	{
		HttpRequest request = new HttpRequest (fileName, "http://127.0.0.1/" + fileName, 
						       query_options);

		request.Browser = headers;
		request.RequestType = method;

		response = new HttpResponse (outputBuffer);
		try {
			page.ProcessRequest (new HttpContext (request, response));
			SendData ();
		} catch (Exception e) {
			Console.WriteLine ("Caught exception rendering page:\n" + e.ToString ());
			HttpHelpers.RenderErrorPage (output, 500, "Internal Server Error",
						     "The server failed to render '" + fileName + "'");
		}
	}

	private void SendData ()
	{
		if (response == null || response.StatusCode != 302){
			HttpHelpers.SendStatus (output, 200, "OK");
		} else {
			HttpHelpers.SendStatus (output, 302, "Found");
			HttpHelpers.SendHeader (output, "Location", response.RedirectLocation);
		}
		HttpHelpers.SendHeader (output, "Host", "127.0.0.1"); // FIXME
		HttpHelpers.SendHeader (output, "Content-Type", GetContentType ());
		string content = outputBuffer.ToString ();
		HttpHelpers.SendHeader (output, "Content-Length", content.Length.ToString ());
		output.Write ("\r\n");
		output.Write (content);
	}

	private string GetContentType ()
	{
		int lastDot = fileName.LastIndexOf ('.');
		if (lastDot == -1 || lastDot + 1 == fileName.Length)
			return "application/octet-stream";

		string suffix = fileName.Substring (lastDot + 1).ToUpper ();
		string contentType = mimeHash [suffix] as string;
		if (contentType == null)
			contentType = "application/octet-stream";
		return contentType;
	}

}

class Worker
{
	private TcpClient socket;
	
	public Worker (TcpClient socket)
	{
		this.socket = socket;
	}

	public void Run ()
	{
		Console.WriteLine ("Started processing...");
		StreamWriter stream_output = new StreamWriter (socket.GetStream ());
		HtmlTextWriter output = new HtmlTextWriter (stream_output);
		StreamReader input = new StreamReader (socket.GetStream ());
		try {
			MyWorkerRequest proc = new MyWorkerRequest (input, output, stream_output);
			proc.ProcessRequest ();
		} catch (Exception e) {
			Console.WriteLine ("Caught exception in Worker.Run");
			Console.WriteLine (e.ToString () + "\n" + e.StackTrace);
			output.WriteLine ("<html>\n<title>Error </title>\n<body>\n<pre>\n" + e.ToString () +
					  "\n</pre>\n</body>\n</html>\n");
		}

		// Under MS may be it throws an exception...?
		try {
			output.Flush ();
		} catch (Exception){
		}

		try {
			output.Close ();
		} catch (Exception){
		}

		try {
			input.Close ();
		} catch (Exception){
		}
		//

		socket.Close ();
		Console.WriteLine ("Finished processing...");
	}
}

public class Server
{
	private TcpListener listen_socket;
	private bool started;
	private bool stop;
	private Thread runner;
	private IPEndPoint bind_address;
	private ArrayList workers;

	public Server ()
		: this (IPAddress.Any, 80)
	{
	}

	public Server (int port)
		: this (IPAddress.Any, port)
	{
	}

	public Server (IPAddress address, int port) 
		: this (new IPEndPoint (address, port))
	{
	}
	
	public Server (IPEndPoint bindAddress)
	{
		if (bindAddress == null)
			throw new ArgumentNullException ("bindAddress");

		bind_address = bindAddress;
	}

	public void Start ()
	{
		if (started)
			throw new InvalidOperationException ("The server is already started.");

		workers = new ArrayList ();
		listen_socket = new TcpListener (bind_address);
		listen_socket.Start ();
		runner = new Thread (new ThreadStart (RunServer));
		runner.Start ();
		stop = false;
		Console.WriteLine ("Server started.");
	}

	public void Stop ()
	{
		if (!started)
			throw new InvalidOperationException ("The server is not started.");

		stop = true;	
		listen_socket.Stop ();
		foreach (Thread th in workers)
			if (th.ThreadState != System.Threading.ThreadState.Stopped)
				th.Abort ();
		workers = null;
		Console.WriteLine ("Server stopped.");
	}

	private void RunServer ()
	{
		started = true;
		try {
			TcpClient client;
			int nrequest = 0;
			while (!stop){
				client = listen_socket.AcceptTcpClient ();
				nrequest++;
				if (nrequest % 1000 == 0)
					CleanupWorkers ();

				Console.WriteLine ("Accepted connection.");
				Worker one_shot = new Worker (client);
				Thread worker = new Thread (new ThreadStart (one_shot.Run));
				workers.Add (worker);
				worker.Start ();
			}
		} catch (ThreadAbortException){
		}

		started = false;
	}
	
	private void CleanupWorkers ()
	{
		ArrayList new_workers = new ArrayList ();

		foreach (Thread th in workers)
			if (th.ThreadState != System.Threading.ThreadState.Stopped)
				new_workers.Add (th);

		workers = new_workers;
	}
	
	private static bool useMonoClasses;

	public static bool UseMonoClasses
	{
		get { return useMonoClasses; }
	}

	public static string SystemWeb
	{
		get { return "System.Web"; }
	}

	public static string SystemDrawing
	{
		get { return "System.Drawing"; }
	}

	public static void Launch ()
	{
		int port = 8000;
		bool useMonoClasses_set = false;
		bool port_set = false;
		bool console_set = false;
		string file_name = "";

		if (!Directory.Exists ("output")){
			Console.WriteLine ("Creating directory 'output' where BAT files and \n" + 
					   "comand output will be stored.");
			Directory.CreateDirectory ("output");
		}

		Server server = new Server (port);
		server.Start ();
	}
	
	public static int Main (string [] args)
	{
		Thread t = new Thread (new ThreadStart (Launch));
		Console.WriteLine ("Web Server running");
		t.Start ();

		return 0;
	}
}

}

--=-qIfMv7hquFFSE79UFo9H--


-- 
Gonzalo Paniagua Javier <gonzalo@gnome-db.org>
http://www.gnome-db.org/~gonzalo/