singrdk/base/Applications/WebServer/Request.sg

650 lines
20 KiB
Plaintext

//------------------------------------------------------------------------------
// <copyright company='Microsoft Corporation'>
// Copyright (c) Microsoft Corporation. All rights reserved.
// Information Contained Herein is Proprietary and Confidential.
// </copyright>
//------------------------------------------------------------------------------
namespace Microsoft.Singularity.WebServer
{
using System;
using System.Collections;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Security;
using System.Text;
using System.Threading;
using System.Globalization;
using System.Web;
// Singularity
using Microsoft.SingSharp;
using Microsoft.SingSharp.Runtime;
using Microsoft.Singularity.Channels;
using Microsoft.Singularity;
using Microsoft.Singularity.WebApps;
using Microsoft.Contracts;
//
/* Event types local to this provider */
internal enum WebServerEvent : ushort
{
ProcessRequest = 1
}
internal sealed class Request
{
// Statistics
private static ulong totalServed = 0;
private static ulong totalTime = 0;
private const int MaxChunkLength = 64 * 1024;
private Connection! connection;
private Application application;
// raw request data
private const int maxHeaderBytes = 32*1024;
private byte[] headerBytes;
private int startHeadersOffset;
private int endHeadersOffset;
private ArrayList headerBytesStrings;
// parsed request data
private string verb;
private string url;
private string protocol;
private string path;
private string filePath;
private string pathInfo;
private string queryString;
private byte[] queryStringBytes;
private int contentLength;
private int preloadedContentLength;
private byte[] preloadedContent;
private string allRawHeaders;
private string[][] unknownRequestHeaders;
private string[] knownRequestHeaders;
private bool specialCaseStaticFileHeaders;
// cached response
private bool headersSent;
private int responseStatus;
private StringBuilder responseHeadersBuilder;
private ArrayList responseBodyBytes;
internal Request(Connection! connection)
{
this.connection = connection;
base();
}
internal void Process()
{
ReadAllHeaders();
// Limit to local requests only
if (!connection.IsLocal) {
connection.WriteErrorAndClose(403);
return;
}
if (headerBytes == null || endHeadersOffset < 0 ||
headerBytesStrings == null || headerBytesStrings.Count == 0) {
connection.WriteErrorAndClose(400);
return;
}
if (!ParseRequestLine()) {
return; // ParseRequestLine() has closed the connection
}
Monitoring.Log(Monitoring.Provider.WebServer,
(ushort)WebServerEvent.ProcessRequest,
this.GetUriPath());
ParseHeaders();
ParsePostedContent();
if (verb == "POST" && contentLength > 0 && preloadedContentLength < contentLength) {
connection.Write100Continue();
}
PrepareResponse();
// Time the following block and report it as the request processing time
{
ulong startTime = Processor.CycleCount;
// Find or create an application that can service this request.
application = Application.FindOrCreateApplication(this);
if (application == null) {
connection.WriteErrorAndClose(404);
} else {
application.Process(this);
}
ulong elapsed = Processor.CycleCount - startTime;
Console.WriteLine("served \"" + GetFilePath() + "\" in " + elapsed + " cycles");
totalServed++;
totalTime += elapsed;
}
}
private bool TryReadAllHeaders()
{
// read the first packet (up to 32K)
byte[] headerBytesRead = connection.ReadRequestBytes(maxHeaderBytes);
if (headerBytesRead == null || headerBytesRead.Length == 0)
return false;
if (headerBytes != null) {
// previous partial read
int len = headerBytesRead.Length + headerBytes.Length;
if (len > maxHeaderBytes)
return false;
byte[] bytes = new byte[len];
Buffer.BlockCopy(headerBytes, 0, bytes, 0, headerBytes.Length);
Buffer.BlockCopy(headerBytesRead, 0, bytes, headerBytes.Length, headerBytesRead.Length);
headerBytes = bytes;
}
else {
headerBytes = headerBytesRead;
}
// start parsing
startHeadersOffset = -1;
endHeadersOffset = -1;
headerBytesStrings = new ArrayList();
// find the end of headers
ByteParser parser = new ByteParser(headerBytes);
for (;;) {
ByteString line = parser.ReadLine();
if (line == null) {
break;
}
if (startHeadersOffset < 0) {
startHeadersOffset = parser.CurrentOffset;
}
if (line.IsEmpty) {
endHeadersOffset = parser.CurrentOffset;
break;
}
headerBytesStrings.Add(line);
}
return true;
}
private void ReadAllHeaders()
{
headerBytes = null;
do {
if (!TryReadAllHeaders()) {
// something bad happened
break;
}
}
while (endHeadersOffset < 0); // found \r\n\r\n
}
private bool ParseRequestLine()
{
ByteString requestLine = (ByteString)headerBytesStrings[0];
ByteString[] elems = requestLine.Split(' ');
if (elems == null || elems.Length < 2 || elems.Length > 3) {
connection.WriteErrorAndClose(400);
return false;
}
verb = elems[0].GetString();
ByteString urlBytes = elems[1];
url = urlBytes.GetString();
if (elems.Length == 3) {
protocol = elems[2].GetString();
}
else {
protocol = "HTTP/1.0";
}
// query string
int iqs = urlBytes.IndexOf('?');
if (iqs > 0) {
queryStringBytes = urlBytes.Substring(iqs+1).GetBytes();
}
else {
queryStringBytes = new byte[0];
}
iqs = url.IndexOf('?');
if (iqs > 0) {
path = url.Substring(0, iqs);
queryString = url.Substring(iqs+1);
}
else {
path = url;
queryStringBytes = new byte[0];
}
// url-decode path
if (path.IndexOf('%') >= 0) {
path = HttpUtility.UrlDecode(path, Encoding.UTF8);
}
// path info
int lastDot = path.LastIndexOf('.');
int lastSlh = path.LastIndexOf('/');
if (lastDot >= 0 && lastSlh >= 0 && lastDot < lastSlh && lastSlh < path.Length - 1) {
int ipi = path.IndexOf('/', lastDot);
filePath = path.Substring(0, ipi);
pathInfo = path.Substring(ipi);
}
else {
filePath = path;
pathInfo = String.Empty;
}
return true;
}
private void ParseHeaders()
{
knownRequestHeaders = new string[HttpWorkerRequest.RequestHeaderMaximum];
// construct unknown headers as array list of name1,value1,...
ArrayList headers = new ArrayList();
for (int i = 1; i < headerBytesStrings.Count; i++) {
string s = ((ByteString)headerBytesStrings[i]).GetString();
int c = s.IndexOf(':');
if (c >= 0) {
string name = s.Substring(0, c).Trim();
string value = s.Substring(c + 1).Trim();
// remember
int knownIndex = HttpWorkerRequest.GetKnownRequestHeaderIndex(name);
if (knownIndex >= 0) {
knownRequestHeaders[knownIndex] = value;
}
else {
headers.Add(name);
headers.Add(value);
}
}
}
// copy to array unknown headers
int n = headers.Count / 2;
unknownRequestHeaders = new string[n][];
int j = 0;
for (int i = 0; i < n; i++) {
unknownRequestHeaders[i] = new string[2];
unknownRequestHeaders[i][0] = (string)headers[j++];
unknownRequestHeaders[i][1] = (string)headers[j++];
}
// remember all raw headers as one string
if (headerBytesStrings.Count > 1) {
allRawHeaders = Encoding.UTF8.GetString(headerBytes, startHeadersOffset, endHeadersOffset-startHeadersOffset);
}
else {
allRawHeaders = String.Empty;
}
}
private void ParsePostedContent()
{
contentLength = 0;
preloadedContentLength = 0;
string contentLengthValue = knownRequestHeaders[HttpWorkerRequest.HeaderContentLength];
if (contentLengthValue != null) {
try {
contentLength = Int32.Parse(contentLengthValue);
}
catch {
}
}
if (headerBytes.Length > endHeadersOffset) {
preloadedContentLength = headerBytes.Length - endHeadersOffset;
if (preloadedContentLength > contentLength && contentLength > 0)
preloadedContentLength = contentLength; // don't read more than the content-length
preloadedContent = new byte[preloadedContentLength];
Buffer.BlockCopy(headerBytes, endHeadersOffset, preloadedContent, 0, preloadedContentLength);
}
}
private void PrepareResponse()
{
headersSent = false;
responseStatus = 200;
responseHeadersBuilder = new StringBuilder();
responseBodyBytes = new ArrayList();
}
///////////////////////////////////////////////////////////////////////////////////////////////
// Implementation of HttpWorkerRequest
internal string! GetUriPath()
{
return path;
}
internal string GetQueryString()
{
return queryString;
}
internal byte[] GetQueryStringRawBytes()
{
return queryStringBytes;
}
internal string GetRawUrl()
{
return url;
}
internal string GetHttpVerbName()
{
return verb;
}
internal string GetHttpVersion()
{
return protocol;
}
internal string GetRemoteAddress()
{
return connection.RemoteIP;
}
internal int GetRemotePort()
{
return 0;
}
internal string GetLocalAddress()
{
return connection.LocalIP;
}
internal string GetServerName()
{
string localAddress = GetLocalAddress();
if (localAddress.Equals("127.0.0.1")) {
return "localhost";
}
return localAddress;
}
internal int GetLocalPort()
{
return connection.ListenerPort;
}
internal string GetFilePath()
{
return filePath;
}
internal string GetPathInfo()
{
return pathInfo;
}
internal byte[] GetPreloadedEntityBody()
{
return preloadedContent;
}
[ Microsoft.Contracts.Pure ]
internal bool IsEntireEntityBodyIsPreloaded()
{
return (contentLength == preloadedContentLength);
}
internal int ReadEntityBody(byte[] buffer, int size)
{
int bytesRead = 0;
byte[] bytes = connection.ReadRequestBytes(size);
if (bytes != null && bytes.Length > 0) {
bytesRead = bytes.Length;
Buffer.BlockCopy(bytes, 0, buffer, 0, bytesRead);
}
return bytesRead;
}
internal string GetKnownRequestHeader(int index)
{
return knownRequestHeaders[index];
}
internal string GetUnknownRequestHeader(string name)
{
int n = unknownRequestHeaders.Length;
for (int i = 0; i < n; i++) {
if (string.Compare(name, unknownRequestHeaders[i][0], true) == 0) {
return unknownRequestHeaders[i][1];
}
}
return null;
}
internal string[][] GetUnknownRequestHeaders()
{
return unknownRequestHeaders;
}
// TODO: No support for user-based security yet.
internal IntPtr GetUserToken()
{
// impersonation token
return IntPtr.Zero;
}
internal string GetServerVariable(string name)
{
string s = String.Empty;
switch (name) {
case "ALL_RAW":
s = allRawHeaders;
break;
case "SERVER_PROTOCOL":
s = protocol;
break;
case "AUTH_TYPE":
if (GetUserToken() != IntPtr.Zero)
s = "NTLM";
break;
}
return s;
}
internal void SendStatus(int statusCode, string statusDescription)
{
responseStatus = statusCode;
}
internal void SendKnownResponseHeader(int index, string value)
{
if (headersSent) {
return;
}
switch (index) {
case HttpWorkerRequest.HeaderServer:
case HttpWorkerRequest.HeaderDate:
case HttpWorkerRequest.HeaderConnection:
// ignore these
return;
case HttpWorkerRequest.HeaderAcceptRanges:
if (value == "bytes") {
// use this header to detect when we're processing a static file
specialCaseStaticFileHeaders = true;
return;
}
break;
case HttpWorkerRequest.HeaderExpires:
case HttpWorkerRequest.HeaderLastModified:
if (specialCaseStaticFileHeaders) {
// NOTE: Ignore these for static files. These are generated
// by the StaticFileHandler, but they shouldn't be.
return;
}
break;
}
responseHeadersBuilder.Append(HttpWorkerRequest.GetKnownResponseHeaderName(index));
responseHeadersBuilder.Append(": ");
responseHeadersBuilder.Append(value);
responseHeadersBuilder.Append("\r\n");
}
internal void SendUnknownResponseHeader(string name, string value)
{
if (headersSent)
return;
responseHeadersBuilder.Append(name);
responseHeadersBuilder.Append(": ");
responseHeadersBuilder.Append(value);
responseHeadersBuilder.Append("\r\n");
}
internal void SendCalculatedContentLength(int contentLength)
{
if (!headersSent) {
responseHeadersBuilder.Append("Content-Length: ");
responseHeadersBuilder.Append(contentLength);
responseHeadersBuilder.Append("\r\n");
}
}
internal bool HeadersSent()
{
return headersSent;
}
internal bool IsClientConnected()
{
return connection.Connected;
}
internal void CloseConnection()
{
connection.Close();
}
internal void SendResponseFromMemory(byte[] data, int length)
{
if (length > 0) {
byte[] bytes = new byte[length];
Buffer.BlockCopy(data, 0, bytes, 0, length);
responseBodyBytes.Add(bytes);
}
}
internal void SendResponseFromMemory([Claims] byte[]! in ExHeap data)
{
VContainer<byte> container = new VContainer<byte>(data);
responseBodyBytes.Add(container);
}
internal void FlushResponse(bool finalFlush)
{
try {
if (!headersSent) {
connection.WriteHeaders(responseStatus, responseHeadersBuilder.ToString());
headersSent = true;
}
for (int i = 0; i < responseBodyBytes.Count; i++) {
byte[] bytes = responseBodyBytes[i] as byte[];
VContainer<byte> container = responseBodyBytes[i] as VContainer<byte>;
if (bytes != null) {
connection.WriteBody(bytes, 0, bytes.Length);
} else if (container != null) {
byte[]! in ExHeap exBytes = container.Acquire();
responseBodyBytes[i] = null;
connection.WriteBody(exBytes);
} else {
Debug.Assert(false);
}
}
}
catch(SocketException) {
// If the socket throws an exception, abort trying to write the
// rest of the body.
}
finally {
// Clean up in case an exception caused us to abort partway through
headersSent = true;
for (int i = 0; i < responseBodyBytes.Count; i++) {
VContainer<byte> container = responseBodyBytes[i] as VContainer<byte>;
if (container != null) {
byte[]! in ExHeap exBytes = container.Acquire();
delete exBytes;
responseBodyBytes[i] = null;
}
}
}
responseBodyBytes = new ArrayList();
if (finalFlush) {
connection.Close();
}
}
internal void EndOfRequest()
{
FlushResponse(true);
}
}
}