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