660 lines
22 KiB
C#
660 lines
22 KiB
C#
//------------------------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All Rights Reserved.
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace Microsoft.VisualStudio.WebHost
|
|
{
|
|
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;
|
|
using System.Web.Hosting;
|
|
|
|
// Singularity
|
|
using Microsoft.SingSharp;
|
|
using Microsoft.Contracts;
|
|
using Microsoft.SingSharp.Runtime;
|
|
using Microsoft.Singularity.Channels;
|
|
using Microsoft.Singularity;
|
|
//
|
|
|
|
// Event types local to this provider
|
|
public enum CassiniEvent : ushort
|
|
{
|
|
ProcessRequest = 1
|
|
}
|
|
|
|
internal sealed class Request : SimpleWorkerRequest {
|
|
private const int MaxChunkLength = 64 * 1024;
|
|
|
|
private Host! _host;
|
|
private Connection! _connection;
|
|
|
|
// raw request data
|
|
private const int maxHeaderBytes = 32*1024;
|
|
private byte[] _headerBytes;
|
|
private int _startHeadersOffset;
|
|
private int _endHeadersOffset;
|
|
private ArrayList _headerByteStrings;
|
|
|
|
// parsed request data
|
|
private string _verb;
|
|
private string _url;
|
|
private string _prot;
|
|
|
|
private string _path;
|
|
private string _filePath;
|
|
private new string _pathInfo;
|
|
private string _pathTranslated;
|
|
private new 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;
|
|
|
|
public Request(Host! host, Connection! connection)
|
|
{
|
|
_host = host;
|
|
_connection = connection;
|
|
base(String.Empty, String.Empty, String.Empty, String.Empty, null);
|
|
}
|
|
|
|
public void Process() {
|
|
ReadAllHeaders();
|
|
|
|
// Limit to local requests only
|
|
if (!_connection.IsLocal) {
|
|
_connection.WriteErrorAndClose(403);
|
|
return;
|
|
}
|
|
|
|
if (_headerBytes == null || _endHeadersOffset < 0 ||
|
|
_headerByteStrings == null || _headerByteStrings.Count == 0) {
|
|
_connection.WriteErrorAndClose(400);
|
|
return;
|
|
}
|
|
|
|
if (!ParseRequestLine()) {
|
|
return; // ParseRequestLine() has closed the connection
|
|
}
|
|
|
|
Monitoring.Log(Monitoring.Provider.Cassini,
|
|
(ushort)CassiniEvent.ProcessRequest,
|
|
this.GetUriPath());
|
|
|
|
// Check if the path is not well formed or is not for the current app
|
|
bool isClientScriptPath = false;
|
|
if (!_host.IsVirtualPathInApp(_path, out isClientScriptPath)) {
|
|
_connection.WriteErrorAndClose(404);
|
|
return;
|
|
}
|
|
|
|
ParseHeaders();
|
|
|
|
ParsePostedContent();
|
|
|
|
string expectHeader = GetKnownRequestHeader(HeaderExpect);
|
|
|
|
if (_verb == "POST" &&
|
|
_contentLength > 0 &&
|
|
_preloadedContentLength < _contentLength &&
|
|
expectHeader != null &&
|
|
expectHeader.ToLower().IndexOf("100-continue") >= 0) {
|
|
|
|
// Client is expecting 100-continue
|
|
_connection.Write100Continue();
|
|
}
|
|
|
|
PrepareResponse();
|
|
|
|
// Hand over the request to be processed
|
|
Dispatcher.DispatchRequest(this);
|
|
}
|
|
|
|
private bool TryReadAllHeaders() {
|
|
// read the first packet (up to 32K)
|
|
byte[] headerBytes = _connection.ReadRequestBytes(maxHeaderBytes);
|
|
|
|
if (headerBytes == null || headerBytes.Length == 0)
|
|
return false;
|
|
|
|
if (_headerBytes != null) {
|
|
// previous partial read
|
|
int len = headerBytes.Length + _headerBytes.Length;
|
|
if (len > maxHeaderBytes)
|
|
return false;
|
|
|
|
byte[] bytes = new byte[len];
|
|
Buffer.BlockCopy(_headerBytes, 0, bytes, 0, _headerBytes.Length);
|
|
Buffer.BlockCopy(headerBytes, 0, bytes, _headerBytes.Length, headerBytes.Length);
|
|
_headerBytes = bytes;
|
|
}
|
|
else {
|
|
_headerBytes = headerBytes;
|
|
}
|
|
|
|
// start parsing
|
|
_startHeadersOffset = -1;
|
|
_endHeadersOffset = -1;
|
|
_headerByteStrings = 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;
|
|
}
|
|
|
|
_headerByteStrings.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)_headerByteStrings[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) {
|
|
_prot = elems[2].GetString();
|
|
}
|
|
else {
|
|
_prot = "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;
|
|
}
|
|
|
|
_pathTranslated = MapPath(_filePath);
|
|
|
|
return true;
|
|
}
|
|
|
|
private void ParseHeaders() {
|
|
_knownRequestHeaders = new string[RequestHeaderMaximum];
|
|
|
|
// construct unknown headers as array list of name1,value1,...
|
|
ArrayList headers = new ArrayList();
|
|
|
|
for (int i = 1; i < _headerByteStrings.Count; i++) {
|
|
string s = ((ByteString)_headerByteStrings[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 = 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 (_headerByteStrings.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
|
|
|
|
public override string GetUriPath() {
|
|
return _path;
|
|
}
|
|
|
|
public override string GetQueryString() {
|
|
return _queryString;
|
|
}
|
|
|
|
public override byte[] GetQueryStringRawBytes() {
|
|
return _queryStringBytes;
|
|
}
|
|
|
|
public override string GetRawUrl() {
|
|
return _url;
|
|
}
|
|
|
|
public override string GetHttpVerbName() {
|
|
return _verb;
|
|
}
|
|
|
|
public override string GetHttpVersion() {
|
|
return _prot;
|
|
}
|
|
|
|
public override string GetRemoteAddress() {
|
|
return _connection.RemoteIP;
|
|
}
|
|
|
|
public override int GetRemotePort() {
|
|
return 0;
|
|
}
|
|
|
|
public override string GetLocalAddress() {
|
|
return _connection.LocalIP;
|
|
}
|
|
|
|
public override string GetServerName() {
|
|
string localAddress = GetLocalAddress();
|
|
if (localAddress.Equals("127.0.0.1")) {
|
|
return "localhost";
|
|
}
|
|
return localAddress;
|
|
}
|
|
|
|
public override int GetLocalPort() {
|
|
return _host.Port;
|
|
}
|
|
|
|
public override string GetFilePath() {
|
|
return _filePath;
|
|
}
|
|
|
|
public override string GetFilePathTranslated() {
|
|
return _pathTranslated;
|
|
}
|
|
|
|
public override string GetPathInfo() {
|
|
return _pathInfo;
|
|
}
|
|
|
|
public override string GetAppPath() {
|
|
return _host.VirtualPath;
|
|
}
|
|
|
|
public override string GetAppPathTranslated() {
|
|
return _host.PhysicalPath;
|
|
}
|
|
|
|
public override byte[] GetPreloadedEntityBody() {
|
|
return _preloadedContent;
|
|
}
|
|
|
|
[Pure]
|
|
public override bool IsEntireEntityBodyIsPreloaded() {
|
|
return (_contentLength == _preloadedContentLength);
|
|
}
|
|
|
|
public override 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;
|
|
}
|
|
|
|
public override string GetKnownRequestHeader(int index) {
|
|
return _knownRequestHeaders[index];
|
|
}
|
|
|
|
public override 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;
|
|
}
|
|
|
|
public override string[][] GetUnknownRequestHeaders() {
|
|
return _unknownRequestHeaders;
|
|
}
|
|
|
|
public override string GetServerVariable(string name) {
|
|
string s = String.Empty;
|
|
|
|
switch (name) {
|
|
case "ALL_RAW":
|
|
s = _allRawHeaders;
|
|
break;
|
|
|
|
case "SERVER_PROTOCOL":
|
|
s = _prot;
|
|
break;
|
|
|
|
case "AUTH_TYPE":
|
|
if (GetUserToken() != IntPtr.Zero)
|
|
s = "NTLM";
|
|
break;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
public override string MapPath(string path) {
|
|
string mappedPath = String.Empty;
|
|
bool isClientScriptPath = false;
|
|
|
|
if (path == null || path.Length == 0 || path.Equals("/")) {
|
|
// asking for the site root
|
|
if (_host.VirtualPath == "/") {
|
|
// app at the site root
|
|
mappedPath = _host.PhysicalPath;
|
|
}
|
|
else {
|
|
// unknown site root - don't point to app root to avoid double config inclusion
|
|
mappedPath = "c:\\"; //Environment.SystemDirectory;
|
|
}
|
|
}
|
|
else if (_host.IsVirtualPathAppPath(path)) {
|
|
// application path
|
|
mappedPath = _host.PhysicalPath;
|
|
}
|
|
else if (_host.IsVirtualPathInApp(path, out isClientScriptPath)) {
|
|
if (isClientScriptPath) {
|
|
mappedPath = _host.PhysicalClientScriptPath + path.Substring(_host.NormalizedClientScriptPath.Length);
|
|
}
|
|
else {
|
|
// inside app but not the app path itself
|
|
mappedPath = _host.PhysicalPath + path.Substring(_host.NormalizedVirtualPath.Length);
|
|
}
|
|
}
|
|
else {
|
|
// outside of app -- make relative to app path
|
|
if (path.StartsWith("/")) {
|
|
mappedPath = _host.PhysicalPath + path.Substring(1);
|
|
}
|
|
else {
|
|
mappedPath = _host.PhysicalPath + path;
|
|
}
|
|
}
|
|
|
|
mappedPath = mappedPath.Replace('/', '\\');
|
|
|
|
if (mappedPath.EndsWith("\\") && !mappedPath.EndsWith(":\\")) {
|
|
mappedPath = mappedPath.Substring(0, mappedPath.Length-1);
|
|
}
|
|
|
|
return mappedPath;
|
|
}
|
|
|
|
public override void SendStatus(int statusCode, string statusDescription) {
|
|
_responseStatus = statusCode;
|
|
}
|
|
|
|
public override 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(GetKnownResponseHeaderName(index));
|
|
_responseHeadersBuilder.Append(": ");
|
|
_responseHeadersBuilder.Append(value);
|
|
_responseHeadersBuilder.Append("\r\n");
|
|
}
|
|
|
|
public override void SendUnknownResponseHeader(string name, string value) {
|
|
if (_headersSent)
|
|
return;
|
|
|
|
_responseHeadersBuilder.Append(name);
|
|
_responseHeadersBuilder.Append(": ");
|
|
_responseHeadersBuilder.Append(value);
|
|
_responseHeadersBuilder.Append("\r\n");
|
|
}
|
|
|
|
public override void SendCalculatedContentLength(int contentLength) {
|
|
if (!_headersSent) {
|
|
_responseHeadersBuilder.Append("Content-Length: ");
|
|
_responseHeadersBuilder.Append(contentLength);
|
|
_responseHeadersBuilder.Append("\r\n");
|
|
}
|
|
}
|
|
|
|
public override bool HeadersSent() {
|
|
return _headersSent;
|
|
}
|
|
|
|
public override bool IsClientConnected() {
|
|
return _connection.Connected;
|
|
}
|
|
|
|
public override void CloseConnection() {
|
|
_connection.Close();
|
|
}
|
|
|
|
public override void SendResponseFromMemory(byte[] data, int length) {
|
|
if (length > 0) {
|
|
byte[] bytes = new byte[length];
|
|
Buffer.BlockCopy(data, 0, bytes, 0, length);
|
|
_responseBodyBytes.Add(bytes);
|
|
}
|
|
}
|
|
|
|
public void SendResponseFromMemory([Claims] byte[]! in ExHeap data) {
|
|
VContainer<byte> container = new VContainer<byte>(data);
|
|
_responseBodyBytes.Add(container);
|
|
}
|
|
|
|
public override 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();
|
|
}
|
|
}
|
|
|
|
public override void EndOfRequest() {
|
|
FlushResponse(true);
|
|
}
|
|
}
|
|
}
|