Imported Upstream version 4.0.0~alpha1

Former-commit-id: 806294f5ded97629b74c85c09952f2a74fe182d9
This commit is contained in:
Jo Shields
2015-04-07 09:35:12 +01:00
parent 283343f570
commit 3c1f479b9d
22469 changed files with 2931443 additions and 869343 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,209 @@
//------------------------------------------------------------------------------
// <copyright file="AspNetWebSocketContext.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.WebSockets {
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.WebSockets;
using System.Security.Principal;
using System.Web.Caching;
using System.Web.Profile;
// Mockable context object that's similar to HttpContextBase, but for WebSocket requests
public abstract class AspNetWebSocketContext : WebSocketContext {
//Maps to HttpRequest.AnonymousID
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ID", Justification = @"Inline with HttpRequest")]
public virtual string AnonymousID {
get { throw new NotImplementedException(); }
}
// Maps to HttpContext.Application
public virtual HttpApplicationStateBase Application {
get { throw new NotImplementedException(); }
}
//Maps to HttpRequest.ApplicationPath
public virtual string ApplicationPath {
get { throw new NotImplementedException(); }
}
//Access to the ASP.NET Cache object normally available off of
//HttpContext.Current.Cache
public virtual Cache Cache {
get { throw new NotImplementedException(); }
}
//Access to the client certificate (if any)
public virtual HttpClientCertificate ClientCertificate {
get { throw new NotImplementedException(); }
}
// returns the number of active WebSockets connections (DevDiv #200247)
public static int ConnectionCount {
get { return AspNetWebSocketManager.Current.ActiveSocketCount; }
}
public override CookieCollection CookieCollection {
get { throw new NotImplementedException(); }
}
//Access to cookies using ASP.NET cookie types
public virtual HttpCookieCollection Cookies {
get { throw new NotImplementedException(); }
}
//maps to HttpRequest.FilePath
public virtual string FilePath {
get { throw new NotImplementedException(); }
}
public override NameValueCollection Headers {
get { throw new NotImplementedException(); }
}
public override bool IsAuthenticated {
get { throw new NotImplementedException(); }
}
//Can be used by the websocket developer to detect if the underlying
//TCP/IP connection is still alive.
public virtual bool IsClientConnected {
get { throw new NotImplementedException(); }
}
//maps to HttpContext.IsDebuggingEnabled
public virtual bool IsDebuggingEnabled {
get { throw new NotImplementedException(); }
}
public override bool IsLocal {
get { throw new NotImplementedException(); }
}
public override bool IsSecureConnection {
get { throw new NotImplementedException(); }
}
// maps to HttpContext.Items
public virtual IDictionary Items {
get { throw new NotImplementedException(); }
}
//Access to the underlying IIS security token for the current request.
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Logon", Justification = @"Inline with HttpRequest.LogonUserIdentity")]
public virtual WindowsIdentity LogonUserIdentity {
get { throw new NotImplementedException(); }
}
public override string Origin
{
get { throw new NotImplementedException(); }
}
//Maps to HttpRequest.Path
public virtual string Path {
get { throw new NotImplementedException(); }
}
//Maps to HttpRequest.PathInfo
public virtual string PathInfo {
get { throw new NotImplementedException(); }
}
//Maps to HttpContext.Profile
public virtual ProfileBase Profile {
get { throw new NotImplementedException(); }
}
//The query-string of the websocket request Url
public virtual NameValueCollection QueryString {
get { throw new NotImplementedException(); }
}
//The raw request Url exposes as a string
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = @"Inline with HttpRequest.RawUrl")]
public virtual string RawUrl {
get { throw new NotImplementedException(); }
}
public override Uri RequestUri {
get { throw new NotImplementedException(); }
}
public override string SecWebSocketKey {
get { throw new NotImplementedException(); }
}
public override IEnumerable<string> SecWebSocketProtocols {
get { throw new NotImplementedException(); }
}
public override string SecWebSocketVersion {
get { throw new NotImplementedException(); }
}
// Maps to HttpContext.Server
public virtual HttpServerUtilityBase Server {
get { throw new NotImplementedException(); }
}
// Maps to HttpRequest.ServerVariables
public virtual NameValueCollection ServerVariables {
get { throw new NotImplementedException(); }
}
//Maps to HttpContext.Timestamp
public virtual DateTime Timestamp {
get { throw new NotImplementedException(); }
}
// Maps to HttpRequest.Unvalidated
public virtual UnvalidatedRequestValuesBase Unvalidated {
get { throw new NotImplementedException(); }
}
//Same as HttpRequest.UrlReferrer
public virtual Uri UrlReferrer {
get { throw new NotImplementedException(); }
}
public override IPrincipal User {
get { throw new NotImplementedException(); }
}
//Same as HttpRequest.UserAgent
public virtual string UserAgent {
get { throw new NotImplementedException(); }
}
//Same as HttpRequest.UserHostAddress
public virtual string UserHostAddress {
get { throw new NotImplementedException(); }
}
//Same as HttpRequest.UserHostName
public virtual string UserHostName {
get { throw new NotImplementedException(); }
}
//Same as HttpRequest.UserLanguages
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = @"Inline with HttpRequest.UserLanguages")]
public virtual string[] UserLanguages {
get { throw new NotImplementedException(); }
}
public override WebSocket WebSocket {
get { throw new NotImplementedException(); }
}
}
}

View File

@@ -0,0 +1,282 @@
//------------------------------------------------------------------------------
// <copyright file="AspNetWebSocketContextImpl.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.WebSockets {
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net;
using System.Net.WebSockets;
using System.Security.Principal;
using System.Web.Caching;
using System.Web.Profile;
// Concrete implementation of AspNetWebSocketContext
internal sealed class AspNetWebSocketContextImpl : AspNetWebSocketContext {
// The HttpContextBase below isn't the entire HttpContext object graph,
// but rather a very small subset that contains only properties relevant
// to a WebSockets request. This should prevent the Gen 2 heap from being
// spammed with data that's irrelevant to a WebSockets request.
private readonly HttpContextBase _httpContext;
private readonly HttpWorkerRequest _workerRequest;
private readonly AspNetWebSocket _webSocket;
private CookieCollection _cookieCollection;
public AspNetWebSocketContextImpl(HttpContextBase httpContext = null, HttpWorkerRequest workerRequest = null, AspNetWebSocket webSocket = null) {
_httpContext = httpContext;
_workerRequest = workerRequest;
_webSocket = webSocket;
}
public override string AnonymousID {
get {
return _httpContext.Request.AnonymousID;
}
}
public override HttpApplicationStateBase Application {
get {
return _httpContext.Application;
}
}
public override string ApplicationPath {
get {
return _httpContext.Request.ApplicationPath;
}
}
public override Cache Cache {
get {
return _httpContext.Cache;
}
}
public override HttpClientCertificate ClientCertificate {
get {
return _httpContext.Request.ClientCertificate;
}
}
public override CookieCollection CookieCollection {
get {
if (_cookieCollection == null) {
// converts a System.Web.HttpCookieCollection to a System.Net.CookieCollection
CookieCollection cc = new CookieCollection();
HttpCookieCollection hcc = Cookies;
for (int i = 0; i < hcc.Count; i++) {
HttpCookie cookie = hcc.Get(i);
cc.Add(new Cookie {
Name = cookie.Name,
Value = cookie.Value,
HttpOnly = cookie.HttpOnly,
Path = cookie.Path,
Secure = cookie.Secure,
Domain = cookie.Domain,
Expires = cookie.Expires
});
}
_cookieCollection = cc;
}
return _cookieCollection;
}
}
public override HttpCookieCollection Cookies {
get {
return _httpContext.Request.Cookies;
}
}
public override string FilePath {
get {
return _httpContext.Request.FilePath;
}
}
public override NameValueCollection Headers {
get {
return _httpContext.Request.Headers;
}
}
public override bool IsAuthenticated {
get {
return _httpContext.Request.IsAuthenticated;
}
}
public override bool IsClientConnected {
get {
return _workerRequest.IsClientConnected();
}
}
public override bool IsDebuggingEnabled {
get {
return _httpContext.IsDebuggingEnabled;
}
}
public override bool IsLocal {
get {
return _httpContext.Request.IsLocal;
}
}
public override bool IsSecureConnection {
get {
return _httpContext.Request.IsSecureConnection;
}
}
public override IDictionary Items {
get {
return _httpContext.Items;
}
}
public override WindowsIdentity LogonUserIdentity {
get {
return _httpContext.Request.LogonUserIdentity;
}
}
public override string Origin
{
get
{
return Headers["Origin"];
}
}
public override string Path {
get {
return _httpContext.Request.Path;
}
}
public override string PathInfo {
get {
return _httpContext.Request.PathInfo;
}
}
public override ProfileBase Profile {
get {
return _httpContext.Profile;
}
}
public override NameValueCollection QueryString {
get {
return _httpContext.Request.QueryString;
}
}
public override string RawUrl {
get {
return _httpContext.Request.RawUrl;
}
}
public override Uri RequestUri {
get {
return _httpContext.Request.Url;
}
}
public override string SecWebSocketKey {
get {
return Headers["Sec-WebSocket-Key"];
}
}
public override IEnumerable<string> SecWebSocketProtocols {
get {
return _httpContext.WebSocketRequestedProtocols;
}
}
public override string SecWebSocketVersion {
get {
return Headers["Sec-WebSocket-Version"];
}
}
public override HttpServerUtilityBase Server {
get {
return _httpContext.Server;
}
}
public override NameValueCollection ServerVariables {
get {
return _httpContext.Request.ServerVariables;
}
}
public override DateTime Timestamp {
get {
return _httpContext.Timestamp;
}
}
public override UnvalidatedRequestValuesBase Unvalidated {
get {
return _httpContext.Request.Unvalidated;
}
}
public override Uri UrlReferrer {
get {
return _httpContext.Request.UrlReferrer;
}
}
public override IPrincipal User {
get {
return _httpContext.User;
}
}
public override string UserAgent {
get {
return _httpContext.Request.UserAgent;
}
}
public override string UserHostAddress {
get {
return _httpContext.Request.UserHostAddress;
}
}
public override string UserHostName {
get {
return _httpContext.Request.UserHostName;
}
}
public override string[] UserLanguages {
get {
return _httpContext.Request.UserLanguages;
}
}
public override WebSocket WebSocket {
get {
return _webSocket;
}
}
}
}

View File

@@ -0,0 +1,87 @@
//------------------------------------------------------------------------------
// <copyright file="AspNetWebSocketManager.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.WebSockets {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web.Util;
// Keeps track of AspNetWebSocket instances so that they can be aborted en masse,
// such as in the case of an AppDomain shutdown.
internal sealed class AspNetWebSocketManager {
public static readonly AspNetWebSocketManager Current = new AspNetWebSocketManager(PerfCounters.Instance);
private bool _aborted;
internal readonly HashSet<IAsyncAbortableWebSocket> _activeSockets = new HashSet<IAsyncAbortableWebSocket>(); // internal only for unit testing purposes
private readonly IPerfCounters _perfCounters;
internal AspNetWebSocketManager(IPerfCounters perfCounters) {
_perfCounters = perfCounters;
}
public int ActiveSocketCount {
get {
// We acquire a full lock when reading the count, similar to how the collections
// in the System.Collections.Concurrent namespace operate.
lock (_activeSockets) {
return _activeSockets.Count;
}
}
}
// Calls Abort() on each tracked socket, then blocks until all have been aborted
public void AbortAllAndWait() {
// Make a copy so we're not iterating over the original collection asynchronously;
// keep the lock for as short a duration as possible.
IAsyncAbortableWebSocket[] sockets;
lock (_activeSockets) {
_aborted = true;
sockets = _activeSockets.ToArray();
}
Task[] abortTasks = Array.ConvertAll(sockets, socket => socket.AbortAsync());
Task.WaitAll(abortTasks);
}
// Begins tracking a socket, calling Abort() if there was an earlier call to AbortAll()
public void Add(IAsyncAbortableWebSocket webSocket) {
int activeSocketCount;
bool shouldAbort;
// keep the lock for as short a period as possible
lock (_activeSockets) {
_activeSockets.Add(webSocket);
activeSocketCount = _activeSockets.Count;
shouldAbort = _aborted;
}
// perform any additional operations outside the lock
_perfCounters.SetCounter(AppPerfCounter.REQUESTS_EXECUTING_WEBSOCKETS, activeSocketCount);
if (shouldAbort) {
webSocket.AbortAsync(); // don't care about the result of the abort at the present time
}
}
// Stops tracking a socket
public void Remove(IAsyncAbortableWebSocket webSocket) {
int activeSocketCount;
// keep the lock for as short a period as possible
lock (_activeSockets) {
_activeSockets.Remove(webSocket);
activeSocketCount = _activeSockets.Count;
}
// perform any additional operations outside the lock
_perfCounters.SetCounter(AppPerfCounter.REQUESTS_EXECUTING_WEBSOCKETS, activeSocketCount);
}
}
}

View File

@@ -0,0 +1,39 @@
//------------------------------------------------------------------------------
// <copyright file="AspNetWebSocketOptions.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.WebSockets {
using System;
using System.Linq;
// Specifies configuration settings for a WebSocket connection
public sealed class AspNetWebSocketOptions {
private string _subProtocol;
// Flag specifying whether ASP.NET should check that the URL that initiated
// the WebSockets connection corresponds to this server, since WebSockets
// (unlike XmlHttpRequest) does not by default have a same-origin restriction.
// See comments in WebSocketUtil for more info.
public bool RequireSameOrigin { get; set; }
// Corresponds to the "subprotocol" that will be sent from the server
// to the client (see WebSockets spec, Sec. 5.2.2). Set to null (default)
// to suppress sending a protocol.
public string SubProtocol {
get {
return _subProtocol;
}
set {
if (value != null && !SubProtocolUtil.IsValidSubProtocolName(value)) {
throw new ArgumentOutOfRangeException("value");
}
_subProtocol = value;
}
}
}
}

View File

@@ -0,0 +1,19 @@
//------------------------------------------------------------------------------
// <copyright file="IAsyncAbortableWebSocket.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.WebSockets {
using System;
using System.Threading.Tasks;
// Represents a WebSocket that can be asynchronously aborted.
internal interface IAsyncAbortableWebSocket {
// Asynchronously aborts a WebSocket.
Task AbortAsync();
}
}

View File

@@ -0,0 +1,32 @@
//------------------------------------------------------------------------------
// <copyright file="IUnmanagedWebSocketContext.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.WebSockets {
using System;
using System.Security.Permissions;
// This interface matches the unmanaged IWebSocketContext interface
internal interface IUnmanagedWebSocketContext {
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
int WriteFragment(IntPtr pData, ref int pcbSent, bool fAsync, bool fUtf8Encoded, bool fFinalFragment, IntPtr pfnCompletion, IntPtr pvCompletionContext, out bool pfCompletionExpected);
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
int ReadFragment(IntPtr pData, ref int pcbData, bool fAsync, out bool pfUtf8Encoded, out bool pfFinalFragment, out bool pfConnectionClose, IntPtr pfnCompletion, IntPtr pvCompletionContext, out bool pfCompletionExpected);
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
int SendConnectionClose(bool fAsync, ushort uStatusCode, string szReason, IntPtr pfnCompletion, IntPtr pvCompletionContext, out bool pfCompletionExpected);
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
int GetCloseStatus(out ushort pStatusCode, out IntPtr ppszReason, out ushort pcchReason);
// can be used for both normal + exception operation
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
void CloseTcpConnection();
}
}

View File

@@ -0,0 +1,32 @@
//------------------------------------------------------------------------------
// <copyright file="IWebSocketPipe.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.WebSockets {
using System;
using System.Net.WebSockets;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Threading.Tasks;
using System.Web.Util;
// Provides an abstraction over the WebSocketPipe
internal interface IWebSocketPipe {
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
void CloseTcpConnection();
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
Task<WebSocketReceiveResult> ReadFragmentAsync(ArraySegment<byte> buffer);
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
Task WriteCloseFragmentAsync(WebSocketCloseStatus closeStatus, string statusDescription);
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
Task WriteFragmentAsync(ArraySegment<byte> buffer, bool isUtf8Encoded, bool isFinalFragment);
}
}

View File

@@ -0,0 +1,133 @@
//------------------------------------------------------------------------------
// <copyright file="SubProtocolUtil.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.WebSockets {
using System;
using System.Collections.Generic;
using System.Linq;
// Utility class for creating and parsing "Sec-WebSocket-Protocol" headers
//
// From the WebSocket protocol spec, sec. 4.1:
// 10. The request MAY include a header field with the name "Sec-
// WebSocket-Protocol". If present, this value indicates one or
// more comma separated subprotocol the client wishes to speak,
// ordered by preference. The elements that comprise this value
// MUST be non-empty strings with characters in the range U+0021 to
// U+007E not including separator characters as defined in
// [RFC2616], and MUST all be unique strings. The ABNF for the
// value of this header field is 1#token, where the definitions of
// constructs and rules are as given in [RFC2616].
//
// RFC 2616, sec. 2.1:
// #rule
// A construct "#" is defined, similar to "*", for defining lists of
// elements. The full form is "<n>#<m>element" indicating at least
// <n> and at most <m> elements, each separated by one or more commas
// (",") and OPTIONAL linear white space (LWS). This makes the usual
// form of lists very easy; a rule such as
// ( *LWS element *( *LWS "," *LWS element ))
// can be shown as
// 1#element
// Wherever this construct is used, null elements are allowed, but do
// not contribute to the count of elements present. That is,
// "(element), , (element) " is permitted, but counts as only two
// elements. Therefore, where at least one element is required, at
// least one non-null element MUST be present. Default values are 0
// and infinity so that "#element" allows any number, including zero;
// "1#element" requires at least one; and "1#2element" allows one or
// two.
internal static class SubProtocolUtil {
// RFC 2616, sec. 2.2:
// LWS = [CRLF] 1*( SP | HT )
// We use a subset: _lwsTrimChars = SP | HT
private static readonly char[] _lwsTrimChars = new char[] { ' ', '\t' };
private static readonly char[] _splitChars = new char[] { ',' };
// Returns a value stating whether the specified SubProtocol is valid
public static bool IsValidSubProtocolName(string subprotocol) {
return (!String.IsNullOrEmpty(subprotocol) && subprotocol.All(IsValidSubProtocolChar));
}
private static bool IsValidSubProtocolChar(char c) {
return ('\u0021' <= c && c <= '\u007e' && !IsSeparatorChar(c));
}
// RFC 2616, sec. 2.2:
// separators = "(" | ")" | "<" | ">" | "@"
// | "," | ";" | ":" | "\" | <">
// | "/" | "[" | "]" | "?" | "="
// | "{" | "}" | SP | HT
private static bool IsSeparatorChar(char c) {
switch (c) {
case '(':
case ')':
case '<':
case '>':
case '@':
case ',':
case ';':
case ':':
case '\\':
case '"':
case '/':
case '[':
case ']':
case '?':
case '=':
case '{':
case '}':
case ' ':
case '\t':
return true;
default:
return false;
}
}
// Returns a list of preferred subprotocols by parsing an incoming header value, or null if the incoming header was improperly formatted.
public static List<string> ParseHeader(string headerValue) {
if (headerValue == null) {
// No incoming values
return null;
}
List<string> subprotocols = new List<string>();
foreach (string subprotocolCandidate in headerValue.Split(_splitChars)) {
string subprotocolCandidateTrimmed = subprotocolCandidate.Trim(_lwsTrimChars); // remove LWS according to '#' rule
// skip LWS between commas according to '#' rule
if (subprotocolCandidateTrimmed.Length == 0) {
continue;
}
// reject improperly formatted header values
if (!IsValidSubProtocolName(subprotocolCandidateTrimmed)) {
return null;
}
// otherwise this subprotocol is OK
subprotocols.Add(subprotocolCandidateTrimmed);
}
if (subprotocols.Count == 0) {
// header is improperly formatted (contained no usable values)
return null;
}
if (subprotocols.Distinct(StringComparer.Ordinal).Count() != subprotocols.Count) {
// header is improperly formatted (contained duplicate values)
return null;
}
return subprotocols;
}
}
}

View File

@@ -0,0 +1,103 @@
//------------------------------------------------------------------------------
// <copyright file="UnmanagedWebSocketContext.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.WebSockets {
using System;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
// Concrete implementation of IUnmanagedWebSocketContext, implemented by IIS8 unmanaged WebSocket module
// SECURITY NOTE: All parameters are assumed to have been validated by the caller
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
internal sealed class UnmanagedWebSocketContext : IUnmanagedWebSocketContext {
// the 'this' parameter for the unmanaged interface
private readonly IntPtr _pWebSocketContext;
internal UnmanagedWebSocketContext(IntPtr pWebSocketContext) {
_pWebSocketContext = pWebSocketContext;
}
public int WriteFragment(IntPtr pData, ref int pcbSent, bool fAsync, bool fUtf8Encoded, bool fFinalFragment, IntPtr pfnCompletion, IntPtr pvCompletionContext, out bool pfCompletionExpected) {
return IIS.MgdWebSocketWriteFragment(_pWebSocketContext, pData, ref pcbSent, fAsync, fUtf8Encoded, fFinalFragment, pfnCompletion, pvCompletionContext, out pfCompletionExpected);
}
public int ReadFragment(IntPtr pData, ref int pcbData, bool fAsync, out bool pfUtf8Encoded, out bool pfFinalFragment, out bool pfConnectionClose, IntPtr pfnCompletion, IntPtr pvCompletionContext, out bool pfCompletionExpected) {
return IIS.MgdWebSocketReadFragment(_pWebSocketContext, pData, ref pcbData, fAsync, out pfUtf8Encoded, out pfFinalFragment, out pfConnectionClose, pfnCompletion, pvCompletionContext, out pfCompletionExpected);
}
public int SendConnectionClose(bool fAsync, ushort uStatusCode, string szReason, IntPtr pfnCompletion, IntPtr pvCompletionContext, out bool pfCompletionExpected) {
return IIS.MgdWebSocketSendConnectionClose(_pWebSocketContext, fAsync, uStatusCode, szReason, pfnCompletion, pvCompletionContext, out pfCompletionExpected);
}
public int GetCloseStatus(out ushort pStatusCode, out IntPtr ppszReason, out ushort pcchReason) {
return IIS.MgdWebSocketGetCloseStatus(_pWebSocketContext, out pStatusCode, out ppszReason, out pcchReason);
}
public void CloseTcpConnection() {
IIS.MgdWebSocketCloseTcpConnection(_pWebSocketContext);
}
// API documentation for each method can be found in ndp/fx/src/xsp/webengine/mgdexports.cxx.
[SuppressUnmanagedCodeSecurity]
private static class IIS {
private const string _IIS_NATIVE_DLL = ModName.MGDENG_FULL_NAME;
// Write a data fragment to the provided IWebSocketContext.
[DllImport(_IIS_NATIVE_DLL)]
internal static extern int MgdWebSocketWriteFragment(
IntPtr pContext,
IntPtr pData,
ref int pcbSent,
bool fAsync,
bool fUTF8Encoded,
bool fFinalFragment,
IntPtr pfnCompletion,
IntPtr pvCompletionContext,
out bool pfCompletionExpected);
// Reads a data fragment from the provided IWebSocketContext.
[DllImport(_IIS_NATIVE_DLL)]
internal static extern int MgdWebSocketReadFragment(
IntPtr pContext,
IntPtr pData,
ref int pcbData,
bool fAsync,
out bool pfUTF8Encoded,
out bool pfFinalFragment,
out bool pfConnectionClose,
IntPtr pfnCompletion,
IntPtr pvCompletionContext,
out bool pfCompletionExpected);
// Sends a CLOSE frame to the provided IWebSocketContext.
[DllImport(_IIS_NATIVE_DLL)]
internal static extern int MgdWebSocketSendConnectionClose(
IntPtr pContext,
bool fAsync,
ushort pStatusCode,
[MarshalAs(UnmanagedType.LPWStr)] string pszReason,
IntPtr pfnCompletion,
IntPtr pvCompletionContext,
out bool pfCompletionExpected);
// Gets information on the CLOSE frame sent from the client to the server.
[DllImport(_IIS_NATIVE_DLL)]
internal static extern int MgdWebSocketGetCloseStatus(
IntPtr pContext,
out ushort pStatusCode,
out IntPtr ppszReason,
out ushort pcchReason);
// Closes the TCP connection used by the provided IWebSocketContext.
[DllImport(_IIS_NATIVE_DLL)]
internal static extern void MgdWebSocketCloseTcpConnection(
IntPtr pContext);
}
}
}

View File

@@ -0,0 +1,237 @@
//------------------------------------------------------------------------------
// <copyright file="WebSocketPipe.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.WebSockets {
using System;
using System.Diagnostics.CodeAnalysis;
using System.Net.WebSockets;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Threading.Tasks;
using System.Web.Util;
// Used to send and receive messages over a WebSocket connection
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
internal sealed class WebSocketPipe : IWebSocketPipe {
// Managed representation (bindable as an anonymous delegate) of work that can be called by the thunk
private delegate void CompletionCallback(int hrError, int cbIO, bool fUtf8Encoded, bool fFinalFragment, bool fClose);
// Corresponds to the unmanaged PFN_WEBSOCKET_COMPLETION delegate
private delegate void CompletionCallbackThunk(int hrError, IntPtr pvCompletionContext, int cbIO, bool fUtf8Encoded, bool fFinalFragment, bool fClose);
private static readonly CompletionCallbackThunk _asyncThunk = AsyncCallbackThunk; // need to root the delegate itself so not collected while unmanaged code executing
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources", Justification = @"This is a function pointer whose lifetime lasts for the entire duration of this AppDomain. We never need to release it.")]
private static readonly IntPtr _asyncThunkAddress = Marshal.GetFunctionPointerForDelegate(_asyncThunk);
private readonly IUnmanagedWebSocketContext _context;
private readonly IPerfCounters _perfCounters;
internal WebSocketPipe(IUnmanagedWebSocketContext context, IPerfCounters perfCounters) {
_context = context;
_perfCounters = perfCounters;
}
public Task WriteFragmentAsync(ArraySegment<byte> buffer, bool isUtf8Encoded, bool isFinalFragment) {
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
// The buffer will be read from asynchronously by unmanaged code, so we require that it remain pinned
PinnedArraySegment<byte> pinnedBuffer = new PinnedArraySegment<byte>(buffer);
// Callback will always be called (since it is responsible for cleanup), even if completed synchronously
CompletionCallback callback = (hrError, cbIO, fUtf8Encoded, fFinalFragment, fClose) => {
try {
ThrowExceptionForHR(hrError);
tcs.TrySetResult(null); // regular completion
}
catch (Exception ex) {
tcs.TrySetException(ex); // exceptional completion
}
finally {
// Always free the buffer to prevent a memory leak
pinnedBuffer.Dispose();
}
};
IntPtr completionContext = GCUtil.RootObject(callback);
// update perf counter with count of data written to wire
_perfCounters.IncrementCounter(AppPerfCounter.REQUEST_BYTES_OUT_WEBSOCKETS, pinnedBuffer.Count);
// Call the underlying implementation; WriteFragment should never throw an exception
int bytesSent = pinnedBuffer.Count;
bool completionExpected;
int hr = _context.WriteFragment(
pData: pinnedBuffer.Pointer,
pcbSent: ref bytesSent,
fAsync: true,
fUtf8Encoded: isUtf8Encoded,
fFinalFragment: isFinalFragment,
pfnCompletion: _asyncThunkAddress,
pvCompletionContext: completionContext,
pfCompletionExpected: out completionExpected);
if (!completionExpected) {
// Completed synchronously or error; the thunk and callback together handle cleanup
AsyncCallbackThunk(hr, completionContext, bytesSent, isUtf8Encoded, isFinalFragment, fClose: false);
}
return tcs.Task;
}
public Task WriteCloseFragmentAsync(WebSocketCloseStatus closeStatus, string statusDescription) {
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
// Callback will always be called (since it is responsible for cleanup), even if completed synchronously
CompletionCallback callback = (hrError, cbIO, fUtf8Encoded, fFinalFragment, fClose) => {
try {
ThrowExceptionForHR(hrError);
tcs.TrySetResult(null); // regular completion
}
catch (Exception ex) {
tcs.TrySetException(ex); // exceptional completion
}
};
IntPtr completionContext = GCUtil.RootObject(callback);
// Call the underlying implementation; SendConnectionClose should never throw an exception
bool completionExpected;
int hr = _context.SendConnectionClose(
fAsync: true,
uStatusCode: (ushort)closeStatus,
szReason: statusDescription, // don't need to pin string: CLR marshaler handles managed to unmanaged conversion, and IIS makes local copy for duration of async operation
pfnCompletion: _asyncThunkAddress,
pvCompletionContext: completionContext,
pfCompletionExpected: out completionExpected);
if (!completionExpected) {
// Completed synchronously or error; the thunk and callback together handle cleanup
AsyncCallbackThunk(hr, completionContext, cbIO: 0, fUtf8Encoded: true, fFinalFragment: true, fClose: false);
}
return tcs.Task;
}
public Task<WebSocketReceiveResult> ReadFragmentAsync(ArraySegment<byte> buffer) {
TaskCompletionSource<WebSocketReceiveResult> tcs = new TaskCompletionSource<WebSocketReceiveResult>();
// The buffer will be written to asynchronously by unmanaged code, so we require that it remain pinned
PinnedArraySegment<byte> pinnedBuffer = new PinnedArraySegment<byte>(buffer);
// Callback will always be called (since it is responsible for cleanup), even if completed synchronously
CompletionCallback callback = (hrError, cbIO, fUtf8Encoded, fFinalFragment, fClose) => {
try {
ThrowExceptionForHR(hrError);
WebSocketCloseStatus? closeStatus = null;
string closeStatusDescription = null;
WebSocketMessageType messageType = (fUtf8Encoded) ? WebSocketMessageType.Text : WebSocketMessageType.Binary;
if (fClose) {
// this is a CLOSE frame
messageType = WebSocketMessageType.Close;
WebSocketCloseStatus statusCode;
GetCloseStatus(out statusCode, out closeStatusDescription);
closeStatus = statusCode;
}
else {
// this is a data frame, so update perf counter with count of data read from wire
_perfCounters.IncrementCounter(AppPerfCounter.REQUEST_BYTES_IN_WEBSOCKETS, cbIO);
}
tcs.TrySetResult(new WebSocketReceiveResult(
count: cbIO,
messageType: messageType,
endOfMessage: fFinalFragment,
closeStatus: closeStatus,
closeStatusDescription: closeStatusDescription));
}
catch (Exception ex) {
tcs.TrySetException(ex); // exceptional completion
}
finally {
// Always free the buffer to prevent a memory leak
pinnedBuffer.Dispose();
}
};
IntPtr completionContext = GCUtil.RootObject(callback);
// Call the underlying implementation; ReadFragment should never throw an exception
int bytesRead = pinnedBuffer.Count;
bool isUtf8Encoded;
bool isFinalFragment;
bool isConnectionClose;
bool completionExpected;
int hr = _context.ReadFragment(
pData: pinnedBuffer.Pointer,
pcbData: ref bytesRead,
fAsync: true,
pfUtf8Encoded: out isUtf8Encoded,
pfFinalFragment: out isFinalFragment,
pfConnectionClose: out isConnectionClose,
pfnCompletion: _asyncThunkAddress,
pvCompletionContext: completionContext,
pfCompletionExpected: out completionExpected);
if (!completionExpected) {
// Completed synchronously or error; the thunk and callback together handle cleanup
AsyncCallbackThunk(hr, completionContext, bytesRead, isUtf8Encoded, isFinalFragment, isConnectionClose);
}
return tcs.Task;
}
// Gets the reason (numeric + textual) the client sent a CLOSE frame to the server.
// Returns false if no reason was given.
private void GetCloseStatus(out WebSocketCloseStatus closeStatus, out string closeStatusDescription) {
ushort statusCode;
IntPtr reasonPtr;
ushort reasonLength;
int hr = _context.GetCloseStatus(out statusCode, out reasonPtr, out reasonLength);
if (hr == HResults.E_NOT_SET) {
// This HRESULT is special-cased to mean that a status code has not been provided.
statusCode = 0;
reasonPtr = IntPtr.Zero;
}
else {
// Any other HRESULTs must be treated as exceptional.
ThrowExceptionForHR(hr);
}
// convert the status code and description string
closeStatus = (WebSocketCloseStatus)statusCode;
if (reasonPtr != IntPtr.Zero) {
unsafe {
// return a managed copy of the string (IIS will free the original memory)
closeStatusDescription = new String((char*)reasonPtr, 0, reasonLength);
}
}
else {
closeStatusDescription = null;
}
}
public void CloseTcpConnection() {
_context.CloseTcpConnection();
}
// This thunk dispatches to the appropriate instance continuation when an asynchronous event completes
private static void AsyncCallbackThunk(int hrError, IntPtr pvCompletionContext, int cbIO, bool fUtf8Encoded, bool fFinalFragment, bool fClose) {
// Calling UnrootObject also makes the callback and everything it references eligible for garbage collection
CompletionCallback callback = (CompletionCallback)GCUtil.UnrootObject(pvCompletionContext);
callback(hrError, cbIO, fUtf8Encoded, fFinalFragment, fClose);
}
private static void ThrowExceptionForHR(int hrError) {
// We should homogenize errors coming from the native layer into a WebSocketException.
if (hrError < 0) {
throw new WebSocketException(hrError);
}
}
}
}

View File

@@ -0,0 +1,61 @@
//------------------------------------------------------------------------------
// <copyright file="WebSocketUtil.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.WebSockets {
using System;
using System.Web;
// Contains miscellaneous utility methods for working with WebSockets
internal static class WebSocketUtil {
// Determines whether or not a given HttpWorkerRequest meets the requirements for "same-origin"
// as called out in these two documents:
// - http://tools.ietf.org/html/rfc6454 (Web Origin)
// - http://tools.ietf.org/html/rfc6455 (WebSockets)
public static bool IsSameOriginRequest(HttpWorkerRequest workerRequest) {
string hostHeader = workerRequest.GetKnownRequestHeader(HttpWorkerRequest.HeaderHost);
if (String.IsNullOrEmpty(hostHeader)) {
// RFC 6455 (Sec. 4.1) and RFC 2616 (Sec. 14.23) make the "Host" header mandatory
return false;
}
string secWebSocketOriginHeader = workerRequest.GetUnknownRequestHeader("Origin");
if (String.IsNullOrEmpty(secWebSocketOriginHeader)) {
// RFC 6455 (Sec. 4.1) makes the "Origin" header mandatory for browser clients.
// Phone apps, console clients, and similar non-browser clients aren't required to send the header,
// but this method isn't intended for those use cases anyway, so we can fail them. (Note: it's still
// possible for a non-browser app to send the appropriate Origin header.)
return false;
}
// create URI instances from both the "Host" and the "Origin" headers
Uri hostHeaderUri = null;
Uri originHeaderUri = null;
bool urisCreatedSuccessfully = Uri.TryCreate(workerRequest.GetProtocol() + "://" + hostHeader.Trim(), UriKind.Absolute, out hostHeaderUri) // RFC 2616 (Sec. 14.23): "Host" header doesn't contain the scheme, so we need to prepend
&& Uri.TryCreate(secWebSocketOriginHeader.Trim(), UriKind.Absolute, out originHeaderUri);
if (!urisCreatedSuccessfully) {
// construction of one of the Uri instances failed
return false;
}
// RFC 6454 (Sec. 4), schemes must be normalized to lowercase. (And for WebSockets we only
// support HTTP / HTTPS anyway.)
if (originHeaderUri.Scheme != "http" && originHeaderUri.Scheme != "https") {
return false;
}
// RFC 6454 (Sec. 5), comparisons should be ordinal. The Uri class will automatically
// fill in the Port property using the default value for the scheme if the provided input doesn't
// explicitly contain a port number.
return hostHeaderUri.Scheme == originHeaderUri.Scheme
&& hostHeaderUri.Host == originHeaderUri.Host
&& hostHeaderUri.Port == originHeaderUri.Port;
}
}
}