/* * description: "IIS7 Handler that sends request data to xebra server and receives page to be displayed." * date: "$Date: 2009-05-01 11:33:29 -0700 (Fri, 01 May 2009) $" * revision: "$Revision: 78473 $" * copyright: "Copyright (c) 1985-2007, Eiffel Software." * license: "GPL version 2 see http://www.eiffel.com/licensing/gpl.txt)" * licensing_options: "Commercial license is available at http://www.eiffel.com/licensing" * copying: "" * source: "[ * Eiffel Software * 5949 Hollister Ave #B, Goleta, CA 93117 * Telephone 805-685-1006, Fax 805-685-6869 * Website http://www.eiffel.com * Customer support http://support.eiffel.com * ]" */ using System; using System.Web; using System.IO; using System.Collections.Specialized; namespace Xebra { /// /// The XHandler handles a http request. It creates a request message that is sent /// to the xebra server. Then it receives a response message which contains cookie /// orders and the html text that is then forwardet to IIS. /// class XHandler : IHttpHandler { #region Constants /// /// The key in the request message that represents the start of the html code /// private static string HTML_START = "#H#"; /// /// The key in the request message that represents the start of the headers_in /// private static string HEADERS_IN = "#HI#"; /// /// The key in the request message that represents the start of the headers_out /// private static string HEADERS_OUT = "#HO#"; /// /// The key in the request message that specifies that the request is too big /// private static string TOO_BIG = "#PTB#"; /// /// The key in the request message that represents the start of the SUBPROCESS_ENVIRONMENT_VARS /// private static string SUBPROCESS_ENVIRONMENT_VARS = "#SE#"; /// /// The key in the request message that represents the end of a table /// private static string END = "#E#"; /// /// The string for method GET /// private static string GET = "GET"; /// /// The string for method POST /// private static string POST = "POST"; /// /// The default encoding of the request content /// private static string DEFAULT_ENCODING = "HTTP/1.1"; /// /// The key in the request message that splits two header key/value pairs /// private static string HKEY = "#$#"; /// /// The key in the request message that splits a header key and a header value /// private static string HVALUE = "#%#"; /// /// The key in the request message that represents the start of the arguments /// private static string ARGUMENTS = "#A#"; /// /// The key in the request message that splits two argument key/value pairs /// private static string AKEY = "&"; /// /// The key in the request message that splits a argument key and an argument value /// private static string AVALUE = "="; /// /// The html message that is displayed on an error /// private static string ERRORMSG = "Xebra Handler - Error Report

Xebra Handler - Error Report


Message: Internal Server Error. See error log.


Xebra Handler for IIS7 Community Preview 1.0

"; /// /// The Content-Type for file uploads /// private static string CT_MULTIPART_FORM_DATA = "multipart/form-data"; /// /// The Content-Type for form submits /// private static string CT_APP_FORM_URLENCODED = "application/x-www-form-urlencoded"; /// /// This flag is used to tell xebra server that a file was uploaded using IIS and and it has to be processed differently /// private static string FILE_UPLOAD_FLAG = "#FUPI#"; /// /// This key is put before the original filename /// private static string FILE_NAME = "#FN#"; /// /// The Content-Type string in the input stream of a file upload request /// private static string UPLOAD_CONTENT_TYPE = "Content-Type"; #endregion #region Fields /// /// The log to write debug and error messages /// XLogger log; /// /// The connection to the xebra server /// XServerConnection srv; /// /// The config info read from the web.config file /// XConfig config; #endregion #region IHttpHandler Members /// /// Specifies that this handler does not handle the request exclusively /// public bool IsReusable { get { return true; } } /// /// The main routine of the handler. Creates a request message and sends it to the server. /// /// The current http context public void ProcessRequest(HttpContext context) { string requestMsg; bool ok = false; string responseMsg; HttpRequest request = context.Request; log = new XWindowsLogger(); config = new XConfig(request, log); srv = new XServerConnection(log, config); if (buildRequestMessage(out requestMsg, request)) { if (srv.connect()) { if (srv.sendMessage(requestMsg)) { if (srv.receiveMessage(out responseMsg)) { if (processResponse(ref responseMsg, context)) { context.Response.Write(responseMsg); ok = true; } } } } } if (!ok) { context.Response.Write(ERRORMSG); } } /// /// Parses the request object and generated a request message string that can be sent to the xebra server. /// The request message is of the following format: /// REQUEST = HEADER HEADERS_IN HEADERS_OUT SUBPROCESS_ENVIRONMENT_VARS ARGS; /// /// HEADER = KEY_METHOD KEY_SPACE url KEY_SPACE KEY_HTTP; /// HEADERS_IN = KEY_HI {TABLE_ENTRY} KEY_END; /// HEADERS_OUT = KEY_HO {TABLE_ENTRY} KEY_END; /// SUBPROCESS_ENVIRONMENT_VARS = KEY_SE TABLE_ENTRIES KEY_END; /// TABLE_ENTRY = KEY_T_NAME item_name KEY_T_VALUE item_value; /// ARGS = KEY_ARG args; /// /// KEY_METHOD = "GET" | "POST"; /// KEY_HTTP = "HTTP/1.1" | "HTTP/1.0"; /// KEY_SPACE = " "; /// KEY_HI = "#HI#"; /// KEY_HO = "#HO#"; /// KEY_END = "#E#"; /// KEY_SE = "#SE#"; /// KEY_T_NAME = "#$#"; /// KEY_T_VALUE = "#%#"; /// KEY_ARG = "#A#"; /// /// The string to store the request message /// The request object /// Returns true on success private bool buildRequestMessage(out string requestMsg, HttpRequest request) { requestMsg = request.RequestType + // Method " " + // Space request.RawUrl + // Url " " + DEFAULT_ENCODING + // Encoding HEADERS_IN + EncodeHeader(request.Headers) + END + // HEADERS_IN HEADERS_OUT + END + // No HEADERS_OUT SUBPROCESS_ENVIRONMENT_VARS + END + // No SUBPROCESS_ENV_VARS ARGUMENTS + // ARGS ""; //Check if ContentLength does not exceed MaxUploadSize if (request.ContentLength > config.MaxUploadSize) { requestMsg += TOO_BIG; return true; } /* If there are, read POST or GET parameters into message buffer */ if (request.RequestType.Equals(POST)) { /* If the Content-Type is CT_MULTIPART_FORM_DATA save the post data to a file and don't append it to the message */ if (request.ContentType.StartsWith(CT_MULTIPART_FORM_DATA)) { if (request.Files.Keys.Count == 1) { // Save file to tmp file string tempFileName = getTmpFileName(config.UploadSavePath); requestMsg += FILE_UPLOAD_FLAG + tempFileName; request.Files.Get(0).SaveAs(tempFileName); //Parse header and append filename to requestMsg int bufSize = 100; int maxSize = 2000; byte[] buf = new byte[bufSize]; string header = ""; string[] lines; string[] lineFragments; string filename; do { request.InputStream.Read(buf, 0, bufSize); header += System.Text.ASCIIEncoding.ASCII.GetString(buf); if (header.Length > maxSize) { log.Error("Error parsing uploaded file InputStream: Giving up finding filename."); return false; } } while (!header.Contains(UPLOAD_CONTENT_TYPE)); lines = header.Split('\n'); if (lines.Length >= 2) { lineFragments = lines[1].Split('"'); if (lineFragments.Length >= 4) { filename = lineFragments[3]; requestMsg += FILE_NAME + filename; return true; } else { log.Error("Error parsing uploaded file InputStream: Unknown formatting."); return false; } } else { log.Error("Error parsing uploaded file InputStream: Not enough lines."); return false; } } else { log.Error("Expected uploaded files 1, actual " + request.Files.Keys.Count.ToString() + "."); return false; } } else { /* If the Content-Type is CT_APP_FORM_URLENCODED encode the data in a form layout */ if (request.ContentType.Equals(CT_APP_FORM_URLENCODED)) { requestMsg += EncodeArgs(request.Form); return true; } else { /* In all other cases we just append the whole request body to the message */ byte[] buf = new byte[(int)request.InputStream.Length]; request.InputStream.Read(buf, 0, (int)request.InputStream.Length); requestMsg += System.Text.ASCIIEncoding.ASCII.GetString(buf); return true; } } } else if (request.RequestType.Equals(GET)) { /* If its a GET request simply append the args to the message */ requestMsg += EncodeArgs(request.QueryString); return true; } else { /* If the method is not POST or GET we don't add any arguments */ return true; } } /// /// Processes the received response message. Extracts cookies. /// /// The response mssage /// The context /// Returns true on success private bool processResponse(ref string responseMsg, HttpContext context) { if (responseMsg.Contains(HTML_START)) { XCookieBakery.createCookies(responseMsg, context); responseMsg = responseMsg.Substring(responseMsg.IndexOf(HTML_START) + HTML_START.Length); return true; } else { log.Error("Received response message does not contain HTML_START."); return false; } } /// /// Generates a random file name that does not exist in the specified directory. /// /// The directory in which the file name should be used later /// The generated file name private string getTmpFileName(string dir) { string prefix = "xebra_upload."; Random r = new Random((int)DateTime.Now.Ticks); string fm; do { fm = prefix; for (int i = 0; i < 6; i++) { fm = fm + (char)(97 + Math.Floor(r.NextDouble() * 25)); } } while (File.Exists(dir + fm)); return dir + fm; } #endregion /// /// Encodes pairs of keys and values with header syntax /// /// The pairs /// The encoded pairs private string EncodeHeader(NameValueCollection pairs) { return EncodePair(pairs, HKEY, HVALUE); } /// /// Encodes pairs of keys and values with argument syntax /// /// The pairs /// The encoded pairs private string EncodeArgs(NameValueCollection pairs) { return EncodePair(pairs, AKEY, AVALUE); } /// /// Creates a string out of key value pairs: RESULT = {afix pair.key bfix pair.value} /// /// The pairs /// Is put before keys /// Is put before values /// The encoded pairs private string EncodePair(NameValueCollection pair, string afix, string bfix) { string result = ""; for (int i = 0; i < pair.Count; i++) { result += afix + pair.GetKey(i) + bfix + pair.Get(i); } return result; } } }