1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Collections.Generic; 6 using System.Net.Sockets; 7 using System.Net.WebSockets; 8 using System.Runtime.InteropServices; 9 using System.Security.Principal; 10 using System.Text; 11 using System.Threading; 12 using System.Threading.Tasks; 13 using Xunit; 14 15 namespace System.Net.Tests 16 { 17 public class HttpListenerContextTests : IDisposable 18 { 19 private HttpListenerFactory Factory { get; } 20 private ClientWebSocket Socket { get; } 21 HttpListenerContextTests()22 public HttpListenerContextTests() 23 { 24 Factory = new HttpListenerFactory(); 25 Socket = new ClientWebSocket(); 26 } 27 Dispose()28 public void Dispose() 29 { 30 Factory.Dispose(); 31 Socket.Dispose(); 32 } 33 34 public static bool IsNotWindows7 { get; } = !PlatformDetection.IsWindows7; 35 SubProtocol_TestData()36 public static IEnumerable<object[]> SubProtocol_TestData() 37 { 38 yield return new object[] { new string[0], null }; 39 40 yield return new object[] { new string[] { "MyProtocol1" }, null }; 41 yield return new object[] { new string[] { "MyProtocol" }, "MyProtocol" }; 42 yield return new object[] { new string[] { "MyProtocol" }, "myPROTOCOL" }; 43 44 yield return new object[] { new string[] { "MyProtocol1", "MyProtocol2" }, null }; 45 yield return new object[] { new string[] { "MyProtocol1", "MyProtocol2" }, "MyProtocol2" }; 46 } 47 48 [ConditionalTheory(nameof(IsNotWindows7))] 49 [MemberData(nameof(SubProtocol_TestData))] AcceptWebSocketAsync_ValidSubProtocol_Success(string[] clientProtocols, string serverProtocol)50 public async Task AcceptWebSocketAsync_ValidSubProtocol_Success(string[] clientProtocols, string serverProtocol) 51 { 52 HttpListenerContext context = await GetWebSocketContext(clientProtocols); 53 HttpListenerWebSocketContext socketContext = await context.AcceptWebSocketAsync(serverProtocol); 54 Assert.Equal(serverProtocol, socketContext.WebSocket.SubProtocol); 55 } 56 57 [ConditionalTheory(nameof(IsNotWindows7))] AcceptWebSocketAsync_ValidWebSocket_SetsUpHeadersInResponse()58 public async Task AcceptWebSocketAsync_ValidWebSocket_SetsUpHeadersInResponse() 59 { 60 HttpListenerContext context = await GetWebSocketContext(new string[] { "SubProtocol", "SubProtocol2" }); 61 HttpListenerWebSocketContext socketContext = await context.AcceptWebSocketAsync("SubProtocol"); 62 63 Assert.Equal("SubProtocol", context.Response.Headers["Sec-WebSocket-Protocol"]); 64 Assert.Equal("Upgrade", context.Response.Headers["Connection"], ignoreCase: true); 65 Assert.Null(context.Response.Headers["Sec-WebSocket-Key"]); 66 Assert.Equal(101, context.Response.StatusCode); 67 68 Assert.Equal("SubProtocol, SubProtocol2", socketContext.Headers["Sec-WebSocket-Protocol"]); 69 Assert.Equal(new string[] { "SubProtocol" }, socketContext.SecWebSocketProtocols); 70 71 Assert.NotEmpty(socketContext.Headers["Sec-WebSocket-Key"]); 72 Assert.NotEmpty(socketContext.SecWebSocketKey); 73 74 Assert.Equal("13", socketContext.Headers["Sec-WebSocket-Version"]); 75 Assert.Equal("13", socketContext.SecWebSocketVersion); 76 77 Assert.Equal("Upgrade", socketContext.Headers["Connection"], ignoreCase: true); 78 Assert.Equal("websocket", socketContext.Headers["Upgrade"], ignoreCase: true); 79 } 80 81 [ConditionalFact(nameof(IsNotWindows7))] AcceptWebSocketAsync_ValidWebSocket_SetsUpContextProperties()82 public async Task AcceptWebSocketAsync_ValidWebSocket_SetsUpContextProperties() 83 { 84 Socket.Options.SetRequestHeader("origin", "Browser"); 85 86 HttpListenerContext context = await GetWebSocketContext(new string[] { "SubProtocol" }); 87 HttpListenerWebSocketContext socketContext = await context.AcceptWebSocketAsync("SubProtocol"); 88 89 Assert.Equal(new Uri(Factory.ListeningUrl), socketContext.RequestUri); 90 Assert.NotSame(context.Request.Headers, socketContext.Headers); 91 Assert.Equal("Browser", socketContext.Origin, ignoreCase: true); 92 Assert.NotSame(context.Request.Cookies, socketContext.CookieCollection); 93 Assert.Null(socketContext.User); 94 Assert.False(socketContext.IsAuthenticated); 95 Assert.True(socketContext.IsLocal); 96 Assert.False(socketContext.IsSecureConnection); 97 } 98 99 [ConditionalFact(nameof(IsNotWindows7))] AcceptWebSocketAsync_AuthorizationInHeaders_ThrowsNotImplementedException()100 public async Task AcceptWebSocketAsync_AuthorizationInHeaders_ThrowsNotImplementedException() 101 { 102 Socket.Options.SetRequestHeader("Authorization", "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes("user:password"))); 103 Factory.GetListener().AuthenticationSchemes = AuthenticationSchemes.Basic; 104 105 HttpListenerContext context = await GetWebSocketContext(); 106 Assert.Equal("user", context.User.Identity.Name); 107 108 HttpListenerWebSocketContext webSocketContext = await context.AcceptWebSocketAsync(null); 109 IPrincipal user = webSocketContext.User; 110 111 // Should be copied as User gets disposed when HttpListenerContext is closed. 112 Assert.NotSame(context.User, webSocketContext.User); 113 114 Assert.Equal("user", webSocketContext.User.Identity.Name); 115 Assert.Equal("Basic", webSocketContext.User.Identity.AuthenticationType); 116 } 117 118 [ConditionalFact(nameof(IsNotWindows7))] AcceptWebSocketAsync_UnsupportedProtocol_ThrowsWebSocketException()119 public async Task AcceptWebSocketAsync_UnsupportedProtocol_ThrowsWebSocketException() 120 { 121 HttpListenerContext context = await GetWebSocketContext(new string[] { "MyProtocol" }); 122 await Assert.ThrowsAsync<WebSocketException>(() => context.AcceptWebSocketAsync("MyOtherProtocol")); 123 } 124 125 [ConditionalFact(nameof(IsNotWindows7))] AcceptWebSocketAsync_NoClientSubProtocol_ThrowsWebSocketException()126 public async Task AcceptWebSocketAsync_NoClientSubProtocol_ThrowsWebSocketException() 127 { 128 HttpListenerContext context = await GetWebSocketContext(); 129 await Assert.ThrowsAsync<WebSocketException>(() => context.AcceptWebSocketAsync("SubProtocol")); 130 } 131 132 [ConditionalTheory(nameof(IsNotWindows7))] 133 [InlineData("Connection: ")] 134 [InlineData("Connection: Connection\r\nUpgrade: ")] 135 [InlineData("Connection: Test1\r\nUpgrade: Test2")] 136 [InlineData("Connection: Upgrade\r\nUpgrade: Test2")] 137 [InlineData("Connection: Upgrade\r\nUpgrade: websocket")] 138 [InlineData("Connection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: ")] 139 [InlineData("Connection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 1")] 140 [InlineData("Connection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13")] 141 [InlineData("Connection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: ")] 142 [InlineData("Connection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: Key")] 143 [InlineData("UnknownHeader: random")] AcceptWebSocketAsync_InvalidHeaders_ThrowsWebSocketException(string headers)144 public async Task AcceptWebSocketAsync_InvalidHeaders_ThrowsWebSocketException(string headers) 145 { 146 await GetSocketContext(new string[] { headers }, async context => 147 { 148 await Assert.ThrowsAsync<WebSocketException>(() => context.AcceptWebSocketAsync(null)); 149 }); 150 } 151 152 [ConditionalTheory(nameof(IsNotWindows7))] 153 [InlineData("")] 154 [InlineData(" ")] 155 [InlineData("random(text")] 156 [InlineData("random)text")] 157 [InlineData("random<text")] 158 [InlineData("random>text")] 159 [InlineData("random@text")] 160 [InlineData("random,text")] 161 [InlineData("random;text")] 162 [InlineData("random:text")] 163 [InlineData("random\\text")] 164 [InlineData("random\"text")] 165 [InlineData("random/text")] 166 [InlineData("random[text")] 167 [InlineData("random]text")] 168 [InlineData("random?text")] 169 [InlineData("random=text")] 170 [InlineData("random{text")] 171 [InlineData("random}text")] 172 [InlineData("\x19")] 173 [InlineData("\x7f")] AcceptWebSocketAsync_InvalidSubProtocol_ThrowsArgumentException(string subProtocol)174 public async Task AcceptWebSocketAsync_InvalidSubProtocol_ThrowsArgumentException(string subProtocol) 175 { 176 HttpListenerContext context = await GetWebSocketContext(); 177 await AssertExtensions.ThrowsAsync<ArgumentException>("subProtocol", () => context.AcceptWebSocketAsync(subProtocol)); 178 } 179 180 [ConditionalTheory(nameof(IsNotWindows7))] 181 [InlineData("!")] 182 [InlineData("#")] 183 [InlineData("YouDontKnowMe")] AcceptWebSocketAsync_NoSuchSubProtocol_ThrowsWebSocketException(string subProtocol)184 public async Task AcceptWebSocketAsync_NoSuchSubProtocol_ThrowsWebSocketException(string subProtocol) 185 { 186 HttpListenerContext context = await GetWebSocketContext(); 187 await Assert.ThrowsAsync<WebSocketException>(() => context.AcceptWebSocketAsync(subProtocol)); 188 } 189 190 [ConditionalFact(nameof(IsNotWindows7))] AcceptWebSocketAsync_InvalidKeepAlive_ThrowsWebSocketException()191 public async Task AcceptWebSocketAsync_InvalidKeepAlive_ThrowsWebSocketException() 192 { 193 HttpListenerContext context = await GetWebSocketContext(); 194 195 TimeSpan keepAlive = TimeSpan.FromMilliseconds(-2); 196 await Assert.ThrowsAsync<ArgumentOutOfRangeException>("keepAliveInterval", () => context.AcceptWebSocketAsync(null, keepAlive)); 197 } 198 199 [ConditionalTheory(nameof(IsNotWindows7))] 200 [InlineData(-1)] 201 [InlineData(0)] 202 [InlineData(255)] 203 [InlineData(64 * 1024 + 1)] AcceptWebSocketAsync_InvalidReceiveBufferSize_ThrowsWebSocketException(int receiveBufferSize)204 public async Task AcceptWebSocketAsync_InvalidReceiveBufferSize_ThrowsWebSocketException(int receiveBufferSize) 205 { 206 HttpListenerContext context = await GetWebSocketContext(); 207 await Assert.ThrowsAsync<ArgumentOutOfRangeException>("receiveBufferSize", () => context.AcceptWebSocketAsync(null, receiveBufferSize, TimeSpan.MaxValue)); 208 } 209 210 [ConditionalFact(nameof(IsNotWindows7))] AcceptWebSocketAsync_NullArrayInArraySegment_ThrowsArgumentNullException()211 public async Task AcceptWebSocketAsync_NullArrayInArraySegment_ThrowsArgumentNullException() 212 { 213 HttpListenerContext context = await GetWebSocketContext(); 214 215 ArraySegment<byte> internalBuffer = new FakeArraySegment() { Array = null }.ToActual(); 216 await AssertExtensions.ThrowsAsync<ArgumentNullException>("internalBuffer.Array", () => context.AcceptWebSocketAsync(null, 1024, TimeSpan.MaxValue, internalBuffer)); 217 } 218 219 [ConditionalTheory(nameof(IsNotWindows7))] 220 [InlineData(-1)] 221 [InlineData(11)] AcceptWebSocketAsync_InvalidOffsetInArraySegment_ThrowsArgumentNullException(int offset)222 public async Task AcceptWebSocketAsync_InvalidOffsetInArraySegment_ThrowsArgumentNullException(int offset) 223 { 224 HttpListenerContext context = await GetWebSocketContext(); 225 226 ArraySegment<byte> internalBuffer = new FakeArraySegment() { Array = new byte[10], Offset = offset }.ToActual(); 227 await AssertExtensions.ThrowsAsync<ArgumentOutOfRangeException>("internalBuffer.Offset", () => context.AcceptWebSocketAsync(null, 1024, TimeSpan.MaxValue, internalBuffer)); 228 } 229 230 [ConditionalTheory(nameof(IsNotWindows7))] 231 [InlineData(0, -1)] 232 [InlineData(0, 11)] 233 [InlineData(10, 1)] 234 [InlineData(9, 2)] AcceptWebSocketAsync_InvalidCountInArraySegment_ThrowsArgumentNullException(int offset, int count)235 public async Task AcceptWebSocketAsync_InvalidCountInArraySegment_ThrowsArgumentNullException(int offset, int count) 236 { 237 HttpListenerContext context = await GetWebSocketContext(); 238 239 ArraySegment<byte> internalBuffer = new FakeArraySegment() { Array = new byte[10], Offset = offset, Count = count }.ToActual(); 240 await AssertExtensions.ThrowsAsync<ArgumentOutOfRangeException>("internalBuffer.Count", () => context.AcceptWebSocketAsync(null, 1024, TimeSpan.MaxValue, internalBuffer)); 241 } 242 GetSocketContext(string[] headers, Func<HttpListenerContext, Task> contextAction)243 private async Task GetSocketContext(string[] headers, Func<HttpListenerContext, Task> contextAction) 244 { 245 using (Socket client = Factory.GetConnectedSocket()) 246 { 247 client.Send(Factory.GetContent("1.1", "POST", null, "Text", headers, true)); 248 249 HttpListener listener = Factory.GetListener(); 250 await contextAction(await listener.GetContextAsync()); 251 } 252 } 253 GetWebSocketContext(string[] subProtocols = null)254 private async Task<HttpListenerContext> GetWebSocketContext(string[] subProtocols = null) 255 { 256 if (subProtocols != null) 257 { 258 foreach (string subProtocol in subProtocols) 259 { 260 Socket.Options.AddSubProtocol(subProtocol); 261 } 262 } 263 264 var uriBuilder = new UriBuilder(Factory.ListeningUrl) { Scheme = "ws" }; 265 Task<HttpListenerContext> serverContextTask = Factory.GetListener().GetContextAsync(); 266 267 Task clientConnectTask = Socket.ConnectAsync(uriBuilder.Uri, CancellationToken.None); 268 if (clientConnectTask == await Task.WhenAny(serverContextTask, clientConnectTask)) 269 { 270 await clientConnectTask; 271 Assert.True(false, "Client should not have completed prior to server sending response"); 272 } 273 274 return await serverContextTask; 275 } 276 277 public struct FakeArraySegment 278 { 279 public byte[] Array; 280 public int Offset; 281 public int Count; 282 ToActualSystem.Net.Tests.HttpListenerContextTests.FakeArraySegment283 public ArraySegment<byte> ToActual() 284 { 285 ArraySegmentWrapper wrapper = default(ArraySegmentWrapper); 286 wrapper.Fake = this; 287 return wrapper.Actual; 288 } 289 } 290 291 [StructLayout(LayoutKind.Explicit)] 292 public struct ArraySegmentWrapper 293 { 294 [FieldOffset(0)] public ArraySegment<byte> Actual; 295 [FieldOffset(0)] public FakeArraySegment Fake; 296 } 297 } 298 } 299