//------------------------------------------------------------------------------ // 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 container = new VContainer(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 container = _responseBodyBytes[i] as VContainer; 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 container = _responseBodyBytes[i] as VContainer; 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); } } }