/*
* 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 ReportXebra 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;
}
}
}