├── .gitignore ├── LICENSE ├── Nuget └── SocketHttpListener.nuspec ├── README.md ├── SocketHttpListener.Test ├── HttpConnectionTest.cs ├── LoggerFactory.cs ├── Properties │ └── AssemblyInfo.cs ├── SocketHttpListener.Test.csproj ├── Utility.cs ├── WebsocketTest.cs ├── localhost.pfx └── packages.config ├── SocketHttpListener.sln ├── SocketHttpListener.sln.GhostDoc.xml ├── SocketHttpListener ├── ByteOrder.cs ├── CloseEventArgs.cs ├── CloseStatusCode.cs ├── CompressionMethod.cs ├── ErrorEventArgs.cs ├── Ext.cs ├── Fin.cs ├── HttpBase.cs ├── HttpResponse.cs ├── Mask.cs ├── MessageEventArgs.cs ├── Net │ ├── AuthenticationSchemeSelector.cs │ ├── ChunkStream.cs │ ├── ChunkedInputStream.cs │ ├── CookieHelper.cs │ ├── EndPointListener.cs │ ├── EndPointManager.cs │ ├── HttpConnection.cs │ ├── HttpListener.cs │ ├── HttpListenerBasicIdentity.cs │ ├── HttpListenerContext.cs │ ├── HttpListenerPrefixCollection.cs │ ├── HttpListenerRequest.cs │ ├── HttpListenerResponse.cs │ ├── HttpStatusCode.cs │ ├── HttpStreamAsyncResult.cs │ ├── HttpVersion.cs │ ├── ListenerPrefix.cs │ ├── RequestStream.cs │ ├── ResponseStream.cs │ ├── WebHeaderCollection.cs │ └── WebSockets │ │ ├── HttpListenerWebSocketContext.cs │ │ └── WebSocketContext.cs ├── Opcode.cs ├── PayloadData.cs ├── Properties │ └── AssemblyInfo.cs ├── Rsv.cs ├── SocketHttpListener.csproj ├── WebSocket.cs ├── WebSocketException.cs ├── WebSocketFrame.cs ├── WebSocketState.cs └── packages.config └── packages ├── Moq.4.2.1502.0911 ├── Moq.4.2.1502.0911.nupkg └── lib │ ├── net35 │ ├── Moq.dll │ └── Moq.xml │ ├── net40 │ ├── Moq.dll │ └── Moq.xml │ └── sl4 │ ├── Moq.Silverlight.dll │ └── Moq.Silverlight.xml ├── Patterns.Logging.1.0.0.6 ├── Patterns.Logging.1.0.0.6.nupkg └── lib │ └── portable-net45+win8 │ └── Patterns.Logging.dll ├── WebSocket4Net.0.12 ├── WebSocket4Net.0.12.nupkg ├── lib │ ├── monoandroid22 │ │ └── WebSocket4Net.dll │ ├── monotouch10 │ │ └── WebSocket4Net.dll │ ├── net20 │ │ └── WebSocket4Net.dll │ ├── net35 │ │ └── WebSocket4Net.dll │ ├── net40 │ │ └── WebSocket4Net.dll │ ├── net45 │ │ └── WebSocket4Net.dll │ ├── sl40-windowsphone71 │ │ └── WebSocket4Net.dll │ ├── sl40 │ │ └── WebSocket4Net.dll │ ├── sl50-windowsphone80 │ │ └── WebSocket4Net.dll │ └── sl50 │ │ └── WebSocket4Net.dll └── nuget.key └── repositories.config /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Visual Studio 3 | ################# 4 | 5 | ## Ignore Visual Studio temporary files, build results, and 6 | ## files generated by popular Visual Studio add-ons. 7 | 8 | # User-specific files 9 | *.suo 10 | *.user 11 | *.sln.docstates 12 | 13 | # Build results 14 | 15 | [Dd]ebug/ 16 | [Rr]elease/ 17 | build/ 18 | [Bb]in/ 19 | [Oo]bj/ 20 | 21 | # MSTest test Results 22 | [Tt]est[Rr]esult*/ 23 | [Bb]uild[Ll]og.* 24 | 25 | *_i.c 26 | *_p.c 27 | *.ilk 28 | *.meta 29 | *.obj 30 | *.pch 31 | *.pdb 32 | *.pgc 33 | *.pgd 34 | *.rsp 35 | *.sbr 36 | *.tlb 37 | *.tli 38 | *.tlh 39 | *.tmp 40 | *.tmp_proj 41 | *.log 42 | *.vspscc 43 | *.vssscc 44 | .builds 45 | *.pidb 46 | *.log 47 | *.scc 48 | *.scc 49 | *.psess 50 | *.vsp 51 | *.vspx 52 | *.orig 53 | *.rej 54 | *.sdf 55 | *.opensdf 56 | *.ipch 57 | 58 | # Visual C++ cache files 59 | ipch/ 60 | *.aps 61 | *.ncb 62 | *.opensdf 63 | *.sdf 64 | *.cachefile 65 | 66 | # Visual Studio profiler 67 | *.psess 68 | *.vsp 69 | *.vspx 70 | 71 | # Guidance Automation Toolkit 72 | *.gpState 73 | 74 | # ReSharper is a .NET coding add-in 75 | _ReSharper*/ 76 | *.[Rr]e[Ss]harper 77 | 78 | # TeamCity is a build add-in 79 | _TeamCity* 80 | 81 | # DotCover is a Code Coverage Tool 82 | *.dotCover 83 | 84 | # NCrunch 85 | *.ncrunch* 86 | .*crunch*.local.xml 87 | 88 | # Installshield output folder 89 | [Ee]xpress/ 90 | 91 | # DocProject is a documentation generator add-in 92 | DocProject/buildhelp/ 93 | DocProject/Help/*.HxT 94 | DocProject/Help/*.HxC 95 | DocProject/Help/*.hhc 96 | DocProject/Help/*.hhk 97 | DocProject/Help/*.hhp 98 | DocProject/Help/Html2 99 | DocProject/Help/html 100 | 101 | # Click-Once directory 102 | publish/ 103 | 104 | # Publish Web Output 105 | *.Publish.xml 106 | *.pubxml 107 | 108 | # NuGet Packages Directory 109 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 110 | # packages/ 111 | dlls/ 112 | dllssigned/ 113 | 114 | # Windows Azure Build Output 115 | csx 116 | *.build.csdef 117 | 118 | # Windows Store app package directory 119 | AppPackages/ 120 | 121 | # Others 122 | sql/ 123 | *.Cache 124 | ClientBin/ 125 | [Ss]tyle[Cc]op.* 126 | ~$* 127 | *~ 128 | *.dbmdl 129 | *.[Pp]ublish.xml 130 | *.publishsettings 131 | 132 | # RIA/Silverlight projects 133 | Generated_Code/ 134 | 135 | # Backup & report files from converting an old project file to a newer 136 | # Visual Studio version. Backup files are not needed, because we have git ;-) 137 | _UpgradeReport_Files/ 138 | Backup*/ 139 | UpgradeLog*.XML 140 | UpgradeLog*.htm 141 | 142 | # SQL Server files 143 | App_Data/*.mdf 144 | App_Data/*.ldf 145 | 146 | ############# 147 | ## Windows detritus 148 | ############# 149 | 150 | # Windows image file caches 151 | Thumbs.db 152 | ehthumbs.db 153 | 154 | # Folder config file 155 | Desktop.ini 156 | 157 | # Recycle Bin used on file shares 158 | $RECYCLE.BIN/ 159 | 160 | # Mac crap 161 | .DS_Store 162 | 163 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Media Browser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Nuget/SocketHttpListener.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | SocketHttpListener 5 | 1.0.0.44 6 | SocketHttpListener 7 | Emby Team 8 | ebr,Luke,scottisafool 9 | https://github.com/MediaBrowser/SocketHttpListener 10 | http://www.mb3admin.com/images/mb3icons1-1.png 11 | false 12 | A standalone HttpListener with support for SSL, WebSockets and Mono. 13 | Copyright © Emby 2013 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SocketHttpListener 2 | ================== 3 | 4 | A standalone HttpListener with support for SSL, WebSockets and Mono 5 | 6 | As part of Media Browser Server we needed an http server implementation that could support both WebSockets and Mono together on a single port. 7 | 8 | This code was originally forked from websocket-sharp: 9 | 10 | https://github.com/sta/websocket-sharp 11 | 12 | websocket-sharp was originally a clone of the mono HttpListener found here: 13 | 14 | https://github.com/mono/mono/tree/master/mcs/class/System/System.Net 15 | 16 | It also added WebSocket support. Over time websocket-sharp began to introduce a lot of refactoring whereas I prefer a straight clone of the mono implementation with added web socket support. So I rebased from the mono version and added web socket support. 17 | 18 | In addition, there are a few very minor differences with the mono HttpListener: 19 | 20 | * Added ILogger dependency for application logging 21 | * Resolved an issue parsing http headers from Upnp devices. (We need to submit a pull request to mono for this). 22 | * Worked around a known issue with Socket.AcceptAsync and windows (Also need to submit a pull request). See: https://github.com/MediaBrowser/SocketHttpListener/blob/master/SocketHttpListener/Net/EndPointListener.cs#L170 23 | * I have replaced the BeginGetContext with a simple Action delegate. Unlike the .NET HttpListener this is not hooking into http.sys, therefore the only reason for the internal queue was to match the api. Now the consumer can decide how to handle this. 24 | 25 | ## Available on Nuget 26 | 27 | https://www.nuget.org/packages/SocketHttpListener 28 | -------------------------------------------------------------------------------- /SocketHttpListener.Test/HttpConnectionTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Reflection; 6 | using System.Security.Cryptography.X509Certificates; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Microsoft.VisualStudio.TestTools.UnitTesting; 10 | using Moq; 11 | using Patterns.Logging; 12 | using HttpListener = SocketHttpListener.Net.HttpListener; 13 | 14 | namespace SocketHttpListener.Test 15 | { 16 | [TestClass] 17 | public class HttpConnectionTest 18 | { 19 | private static readonly byte[] BYTES_TO_WRITE = Encoding.UTF8.GetBytes(Utility.TEXT_TO_WRITE); 20 | 21 | private static string pfxLocation; 22 | 23 | private Mock logger; 24 | private HttpListener listener; 25 | private HttpClient httpClient; 26 | 27 | [ClassInitialize] 28 | public static void ClassInit(TestContext context) 29 | { 30 | pfxLocation = Utility.GetCertificateFilePath(); 31 | } 32 | 33 | [ClassCleanup] 34 | public static void ClassCleanup() 35 | { 36 | if (File.Exists(pfxLocation)) 37 | { 38 | File.Delete(pfxLocation); 39 | } 40 | } 41 | 42 | [TestInitialize] 43 | public void TestInit() 44 | { 45 | this.logger = LoggerFactory.CreateLogger(); 46 | this.httpClient = new HttpClient(); 47 | 48 | ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; 49 | } 50 | 51 | [TestCleanup] 52 | public void TestCleanup() 53 | { 54 | ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => false; 55 | 56 | this.logger = null; 57 | ((IDisposable)this.listener).Dispose(); 58 | this.httpClient.Dispose(); 59 | } 60 | 61 | [TestMethod] 62 | public async Task TestHttpsListenAndConnect() 63 | { 64 | CreateListener(pfxLocation); 65 | 66 | await TestListenAndConnect("https"); 67 | } 68 | 69 | [TestMethod] 70 | public async Task TestHttpListenAndConnect() 71 | { 72 | CreateListener(pfxLocation); 73 | 74 | await TestListenAndConnect("http"); 75 | } 76 | 77 | [TestMethod] 78 | public async Task TestHttpListenAndConnectMissingCert() 79 | { 80 | CreateListener(@"C:\d.pfx"); 81 | 82 | await TestListenAndConnect("http"); 83 | } 84 | 85 | [TestMethod] 86 | public async Task TestHttpListenAndConnectNoPrivateKeyCert() 87 | { 88 | string certWithoutKey = Path.GetTempFileName(); 89 | 90 | try 91 | { 92 | RemovePrivateKeyAndWrite(pfxLocation, certWithoutKey); 93 | 94 | CreateListener(certWithoutKey); 95 | 96 | await TestListenAndConnect("http"); 97 | } 98 | finally 99 | { 100 | File.Delete(certWithoutKey); 101 | } 102 | } 103 | 104 | [TestMethod] 105 | public async Task TestHttpListenAndConnectCorruptedCert() 106 | { 107 | 108 | string corruptedCert = Path.GetTempFileName(); 109 | 110 | try 111 | { 112 | File.WriteAllBytes(corruptedCert, new byte[] { 0x01, 0x02 }); 113 | 114 | CreateListener(corruptedCert); 115 | 116 | await TestListenAndConnect("http"); 117 | } 118 | finally 119 | { 120 | File.Delete(corruptedCert); 121 | } 122 | } 123 | 124 | private void RemovePrivateKeyAndWrite(string sourceCertFile, string destCertFile) 125 | { 126 | X509Certificate2 sourceCert = new X509Certificate2(sourceCertFile); 127 | 128 | sourceCert.PrivateKey = null; 129 | 130 | File.WriteAllBytes(destCertFile, sourceCert.Export(X509ContentType.Pfx, (string)null)); 131 | } 132 | 133 | private void CreateListener(string pfxLocationLocal) 134 | { 135 | this.listener = new HttpListener(this.logger.Object, pfxLocationLocal); 136 | } 137 | 138 | private async Task TestListenAndConnect(string prefix) 139 | { 140 | string url = string.Format("{0}://{1}", prefix, Utility.SITE_URL); 141 | this.listener.Prefixes.Add(url); 142 | this.listener.Start(); 143 | this.listener.OnContext = async x => 144 | { 145 | await x.Response.OutputStream.WriteAsync(BYTES_TO_WRITE, 0, BYTES_TO_WRITE.Length); 146 | x.Response.Close(); 147 | }; 148 | 149 | string result = await this.httpClient.GetStringAsync(url); 150 | Assert.AreEqual(Utility.TEXT_TO_WRITE, result); 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /SocketHttpListener.Test/LoggerFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Moq; 8 | using Patterns.Logging; 9 | 10 | namespace SocketHttpListener.Test 11 | { 12 | internal static class LoggerFactory 13 | { 14 | internal static Mock CreateLogger() 15 | { 16 | Mock logger = new Mock(); 17 | 18 | SetupConsoleOutput(logger, x => x.Debug(It.IsAny(), It.IsAny())); 19 | SetupConsoleOutput(logger, x => x.Error(It.IsAny(), It.IsAny())); 20 | SetupConsoleOutput(logger, x => x.Fatal(It.IsAny(), It.IsAny())); 21 | SetupConsoleOutput(logger, x => x.Info(It.IsAny(), It.IsAny())); 22 | SetupConsoleOutput(logger, x => x.Warn(It.IsAny(), It.IsAny())); 23 | SetupConsoleOutputException(logger, x => x.ErrorException(It.IsAny(), It.IsAny(), It.IsAny())); 24 | SetupConsoleOutputException(logger, x => x.FatalException(It.IsAny(), It.IsAny(), It.IsAny())); 25 | 26 | logger.Object.Debug("TEST"); 27 | 28 | return logger; 29 | } 30 | private static void SetupConsoleOutput(Mock logger, Expression> action) 31 | { 32 | logger.Setup(action).Callback(Console.WriteLine); 33 | } 34 | 35 | private static void SetupConsoleOutputException(Mock logger, Expression> action) 36 | { 37 | logger.Setup(action).Callback((x, y, z) => 38 | { 39 | string result = string.Format(x, z); 40 | Console.WriteLine("{0} {1}Exception:{2} {1}Stack:{3}", result, Environment.NewLine, y.Message, 41 | y.StackTrace); 42 | }); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /SocketHttpListener.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SocketHttpListener.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SocketHttpListener.Test")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("7c3c3384-7e6f-4ede-aaff-44a91a395b20")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /SocketHttpListener.Test/SocketHttpListener.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {D1143808-2733-422E-81A4-09F53A7EDEA1} 7 | Library 8 | Properties 9 | SocketHttpListener.Test 10 | SocketHttpListener.Test 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | False 40 | ..\packages\Moq.4.2.1502.0911\lib\net40\Moq.dll 41 | 42 | 43 | ..\packages\Patterns.Logging.1.0.0.6\lib\portable-net45+win8\Patterns.Logging.dll 44 | True 45 | 46 | 47 | 48 | 49 | False 50 | ..\packages\WebSocket4Net.0.12\lib\net45\WebSocket4Net.dll 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | {5D291CE2-AF6C-4072-9D18-389520C4D869} 75 | SocketHttpListener 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | False 87 | 88 | 89 | False 90 | 91 | 92 | False 93 | 94 | 95 | False 96 | 97 | 98 | 99 | 100 | 101 | 102 | 109 | -------------------------------------------------------------------------------- /SocketHttpListener.Test/Utility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SocketHttpListener.Test 10 | { 11 | internal static class Utility 12 | { 13 | internal const string SITE_URL = "localhost:12345/Testing/"; 14 | internal const string TEXT_TO_WRITE = "TESTING12345"; 15 | 16 | private const string CERTIFICATE_RESOURCE_NAME = "SocketHttpListener.Test.localhost.pfx"; 17 | 18 | internal static string GetCertificateFilePath() 19 | { 20 | string pfxLocation = Path.GetTempFileName(); 21 | using (var fileStream = File.OpenWrite(pfxLocation)) 22 | { 23 | Assembly.GetExecutingAssembly().GetManifestResourceStream(CERTIFICATE_RESOURCE_NAME).CopyTo(fileStream); 24 | } 25 | 26 | return pfxLocation; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /SocketHttpListener.Test/WebsocketTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Microsoft.VisualStudio.TestTools.UnitTesting; 10 | using Moq; 11 | using Patterns.Logging; 12 | using SocketHttpListener.Net.WebSockets; 13 | using HttpListener = SocketHttpListener.Net.HttpListener; 14 | 15 | namespace SocketHttpListener.Test 16 | { 17 | [TestClass] 18 | public class WebsocketTest 19 | { 20 | private static readonly TimeSpan WaitOneTimeout = TimeSpan.FromSeconds(10); 21 | 22 | private static string pfxLocation; 23 | 24 | private Mock logger; 25 | private HttpListener listener; 26 | private WebSocket4Net.WebSocket socket; 27 | private AutoResetEvent serverResetEvent; 28 | private AutoResetEvent clientResetEvent; 29 | private WebSocketContext webSocketContextServer; 30 | private bool areEqual; 31 | private bool sent; 32 | 33 | [ClassInitialize] 34 | public static void ClassInit(TestContext context) 35 | { 36 | pfxLocation = Utility.GetCertificateFilePath(); 37 | } 38 | 39 | [ClassCleanup] 40 | public static void ClassCleanup() 41 | { 42 | if (File.Exists(pfxLocation)) 43 | { 44 | File.Delete(pfxLocation); 45 | } 46 | } 47 | 48 | [TestInitialize] 49 | public void TestInit() 50 | { 51 | this.areEqual = false; 52 | this.sent = false; 53 | 54 | this.logger = LoggerFactory.CreateLogger(); 55 | this.listener = new HttpListener(this.logger.Object, pfxLocation); 56 | 57 | this.serverResetEvent = new AutoResetEvent(false); 58 | this.clientResetEvent = new AutoResetEvent(false); 59 | 60 | ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; 61 | } 62 | 63 | [TestCleanup] 64 | public void TestCleanup() 65 | { 66 | ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => false; 67 | 68 | this.serverResetEvent.Dispose(); 69 | this.clientResetEvent.Dispose(); 70 | 71 | this.socket.Dispose(); // 0.10 doesn't have this. Comment out to show error. 72 | ((IDisposable)this.listener).Dispose(); 73 | this.logger = null; 74 | this.webSocketContextServer = null; 75 | this.areEqual = false; 76 | this.sent = false; 77 | } 78 | 79 | [TestMethod] 80 | public void LargeWebSocketMessageTest() 81 | { 82 | RunWebSocketMessageTest("http", "ws", string.Concat(Enumerable.Range(0, 3000000).Select(x => Utility.TEXT_TO_WRITE))); 83 | } 84 | 85 | /// 86 | /// This test fails because the largest binary message that can be received correctly by WebSocket4Net is 1016 bytes. 87 | /// The bug in in is: https://github.com/kerryjiang/WebSocket4Net/blob/master/WebSocket4Net/WebSocketCommandInfo.cs#L123 88 | /// copied is never incremented causing binary messages over 1016 bytes to overwrite one another. Once this is fixed binary should work. 89 | /// 90 | [TestMethod] 91 | public void LargeWebSocketDataTest() 92 | { 93 | RunWebSocketDataTest("http", "ws", string.Concat(Enumerable.Range(0, 3000000).Select(x => Utility.TEXT_TO_WRITE))); 94 | } 95 | 96 | 97 | [TestMethod] 98 | public void LargeWebSocketLargestValidDataTest() 99 | { 100 | RunWebSocketDataTest("http", "ws", string.Concat(Enumerable.Range(0, 1016).Select(x => "A"))); 101 | } 102 | 103 | [TestMethod] 104 | public void TestWebSocketHttpListenAndConnect() 105 | { 106 | RunWebSocketMessageTest("http", "ws", Utility.TEXT_TO_WRITE); 107 | } 108 | 109 | [TestMethod] 110 | public void TestWebSocketHttpsListenAndConnect() 111 | { 112 | RunWebSocketMessageTest("https", "wss", Utility.TEXT_TO_WRITE); 113 | } 114 | 115 | private void RunWebSocketDataTest(string httpPrefix, string wsPrefix, string messageToSend) 116 | { 117 | SetupListener(httpPrefix); 118 | 119 | SetupClient(wsPrefix, messageToSend); 120 | 121 | SendAndWaitForResults(Encoding.UTF8.GetBytes(messageToSend)); 122 | } 123 | 124 | private void RunWebSocketMessageTest(string httpPrefix, string wsPrefix, string messageToSend) 125 | { 126 | SetupListener(httpPrefix); 127 | 128 | SetupClient(wsPrefix, messageToSend); 129 | 130 | SendAndWaitForResults(messageToSend); 131 | } 132 | 133 | private void SendAndWaitForResults(string valueToSend) 134 | { 135 | webSocketContextServer.WebSocket.SendAsync(valueToSend, x => 136 | { 137 | sent = x; 138 | this.serverResetEvent.Set(); 139 | }); 140 | 141 | WaitForResults(); 142 | } 143 | 144 | private void SendAndWaitForResults(byte[] valueToSend) 145 | { 146 | webSocketContextServer.WebSocket.SendAsync(valueToSend, x => 147 | { 148 | sent = x; 149 | this.serverResetEvent.Set(); 150 | }); 151 | 152 | WaitForResults(); 153 | } 154 | 155 | private void WaitForResults() 156 | { 157 | Assert.IsTrue(this.serverResetEvent.WaitOne(WaitOneTimeout), "Timeout waiting for message to send"); 158 | Assert.IsTrue(sent, "Message not sent"); 159 | 160 | Assert.IsTrue(this.clientResetEvent.WaitOne(WaitOneTimeout), "Timeout receiving message from server socket"); 161 | Assert.IsTrue(areEqual, "Value sent does not equal value recieved"); 162 | 163 | this.socket.Close(); 164 | 165 | Assert.IsTrue(this.clientResetEvent.WaitOne(WaitOneTimeout), "Timeout waiting for close"); // Wait for close 166 | } 167 | 168 | private void SetupClient(string prefix, string expectedResult) 169 | { 170 | string url = string.Format("{0}://{1}", prefix, Utility.SITE_URL); 171 | 172 | this.socket = new WebSocket4Net.WebSocket(url); 173 | 174 | this.socket.Closed += (sender, args) => 175 | { 176 | this.logger.Object.Info("Socket Closed"); 177 | this.clientResetEvent.Set(); 178 | }; 179 | 180 | this.socket.Opened += (sender, args) => 181 | { 182 | this.logger.Object.Info("Socket Opened"); 183 | this.clientResetEvent.Set(); 184 | }; 185 | 186 | this.socket.MessageReceived += (sender, args) => 187 | { 188 | this.logger.Object.Info("Got Message"); 189 | 190 | this.areEqual = string.Compare(expectedResult, args.Message, StringComparison.Ordinal) == 0; 191 | this.clientResetEvent.Set(); 192 | }; 193 | 194 | this.socket.DataReceived += (sender, args) => 195 | { 196 | this.logger.Object.Info("Got Data"); 197 | 198 | this.areEqual = string.Compare(expectedResult, Encoding.UTF8.GetString(args.Data), StringComparison.Ordinal) == 0; 199 | this.clientResetEvent.Set(); 200 | }; 201 | 202 | this.socket.Open(); 203 | 204 | Assert.IsTrue(this.serverResetEvent.WaitOne(WaitOneTimeout), "Timeout waiting for server to accept connection"); // Wait for server to complete connection 205 | Assert.IsTrue(this.clientResetEvent.WaitOne(WaitOneTimeout), "Timeout waiting for client to open"); // Wait for client Open 206 | } 207 | 208 | private void SetupListener(string prefix) 209 | { 210 | string url = string.Format("{0}://{1}", prefix, Utility.SITE_URL); 211 | this.listener.Prefixes.Add(url); 212 | this.listener.Start(); 213 | 214 | this.listener.OnContext += context => 215 | { 216 | this.logger.Object.Info("Accepting connection."); 217 | 218 | this.webSocketContextServer = context.AcceptWebSocket(null); 219 | this.webSocketContextServer.WebSocket.ConnectAsServer(); 220 | this.serverResetEvent.Set(); 221 | }; 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /SocketHttpListener.Test/localhost.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaBrowser/SocketHttpListener/60d2266f1b31c476611425147dd56302e2640824/SocketHttpListener.Test/localhost.pfx -------------------------------------------------------------------------------- /SocketHttpListener.Test/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /SocketHttpListener.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SocketHttpListener", "SocketHttpListener\SocketHttpListener.csproj", "{5D291CE2-AF6C-4072-9D18-389520C4D869}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SocketHttpListener.Test", "SocketHttpListener.Test\SocketHttpListener.Test.csproj", "{D1143808-2733-422E-81A4-09F53A7EDEA1}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {5D291CE2-AF6C-4072-9D18-389520C4D869}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {5D291CE2-AF6C-4072-9D18-389520C4D869}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {5D291CE2-AF6C-4072-9D18-389520C4D869}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {5D291CE2-AF6C-4072-9D18-389520C4D869}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {D1143808-2733-422E-81A4-09F53A7EDEA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {D1143808-2733-422E-81A4-09F53A7EDEA1}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {D1143808-2733-422E-81A4-09F53A7EDEA1}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {D1143808-2733-422E-81A4-09F53A7EDEA1}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | dtLauncher_EnableVSTestHost = 0 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /SocketHttpListener.sln.GhostDoc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | .\Help 13 | SocketHttpListener 14 | 15 | 16 | true 17 | false 18 | false 19 | false 20 | 21 | 22 | true 23 | false 24 | false 25 | false 26 | true 27 | false 28 | 29 | 30 | true 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /SocketHttpListener/ByteOrder.cs: -------------------------------------------------------------------------------- 1 | namespace SocketHttpListener 2 | { 3 | /// 4 | /// Contains the values that indicate whether the byte order is a Little-endian or Big-endian. 5 | /// 6 | public enum ByteOrder : byte 7 | { 8 | /// 9 | /// Indicates a Little-endian. 10 | /// 11 | Little, 12 | /// 13 | /// Indicates a Big-endian. 14 | /// 15 | Big 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SocketHttpListener/CloseEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace SocketHttpListener 5 | { 6 | /// 7 | /// Contains the event data associated with a event. 8 | /// 9 | /// 10 | /// A event occurs when the WebSocket connection has been closed. 11 | /// If you would like to get the reason for the close, you should access the or 12 | /// property. 13 | /// 14 | public class CloseEventArgs : EventArgs 15 | { 16 | #region Private Fields 17 | 18 | private bool _clean; 19 | private ushort _code; 20 | private string _reason; 21 | 22 | #endregion 23 | 24 | #region Internal Constructors 25 | 26 | internal CloseEventArgs (PayloadData payload) 27 | { 28 | var data = payload.ApplicationData; 29 | var len = data.Length; 30 | _code = len > 1 31 | ? data.SubArray (0, 2).ToUInt16 (ByteOrder.Big) 32 | : (ushort) CloseStatusCode.NoStatusCode; 33 | 34 | _reason = len > 2 35 | ? GetUtf8String(data.SubArray (2, len - 2)) 36 | : String.Empty; 37 | } 38 | 39 | private string GetUtf8String(byte[] bytes) 40 | { 41 | return Encoding.UTF8.GetString(bytes, 0, bytes.Length); 42 | } 43 | 44 | #endregion 45 | 46 | #region Public Properties 47 | 48 | /// 49 | /// Gets the status code for the close. 50 | /// 51 | /// 52 | /// A that represents the status code for the close if any. 53 | /// 54 | public ushort Code { 55 | get { 56 | return _code; 57 | } 58 | } 59 | 60 | /// 61 | /// Gets the reason for the close. 62 | /// 63 | /// 64 | /// A that represents the reason for the close if any. 65 | /// 66 | public string Reason { 67 | get { 68 | return _reason; 69 | } 70 | } 71 | 72 | /// 73 | /// Gets a value indicating whether the WebSocket connection has been closed cleanly. 74 | /// 75 | /// 76 | /// true if the WebSocket connection has been closed cleanly; otherwise, false. 77 | /// 78 | public bool WasClean { 79 | get { 80 | return _clean; 81 | } 82 | 83 | internal set { 84 | _clean = value; 85 | } 86 | } 87 | 88 | #endregion 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /SocketHttpListener/CloseStatusCode.cs: -------------------------------------------------------------------------------- 1 | namespace SocketHttpListener 2 | { 3 | /// 4 | /// Contains the values of the status code for the WebSocket connection close. 5 | /// 6 | /// 7 | /// 8 | /// The values of the status code are defined in 9 | /// Section 7.4 10 | /// of RFC 6455. 11 | /// 12 | /// 13 | /// "Reserved value" must not be set as a status code in a close control frame 14 | /// by an endpoint. It's designated for use in applications expecting a status 15 | /// code to indicate that the connection was closed due to the system grounds. 16 | /// 17 | /// 18 | public enum CloseStatusCode : ushort 19 | { 20 | /// 21 | /// Equivalent to close status 1000. 22 | /// Indicates a normal close. 23 | /// 24 | Normal = 1000, 25 | /// 26 | /// Equivalent to close status 1001. 27 | /// Indicates that an endpoint is going away. 28 | /// 29 | Away = 1001, 30 | /// 31 | /// Equivalent to close status 1002. 32 | /// Indicates that an endpoint is terminating the connection due to a protocol error. 33 | /// 34 | ProtocolError = 1002, 35 | /// 36 | /// Equivalent to close status 1003. 37 | /// Indicates that an endpoint is terminating the connection because it has received 38 | /// an unacceptable type message. 39 | /// 40 | IncorrectData = 1003, 41 | /// 42 | /// Equivalent to close status 1004. 43 | /// Still undefined. Reserved value. 44 | /// 45 | Undefined = 1004, 46 | /// 47 | /// Equivalent to close status 1005. 48 | /// Indicates that no status code was actually present. Reserved value. 49 | /// 50 | NoStatusCode = 1005, 51 | /// 52 | /// Equivalent to close status 1006. 53 | /// Indicates that the connection was closed abnormally. Reserved value. 54 | /// 55 | Abnormal = 1006, 56 | /// 57 | /// Equivalent to close status 1007. 58 | /// Indicates that an endpoint is terminating the connection because it has received 59 | /// a message that contains a data that isn't consistent with the type of the message. 60 | /// 61 | InconsistentData = 1007, 62 | /// 63 | /// Equivalent to close status 1008. 64 | /// Indicates that an endpoint is terminating the connection because it has received 65 | /// a message that violates its policy. 66 | /// 67 | PolicyViolation = 1008, 68 | /// 69 | /// Equivalent to close status 1009. 70 | /// Indicates that an endpoint is terminating the connection because it has received 71 | /// a message that is too big to process. 72 | /// 73 | TooBig = 1009, 74 | /// 75 | /// Equivalent to close status 1010. 76 | /// Indicates that the client is terminating the connection because it has expected 77 | /// the server to negotiate one or more extension, but the server didn't return them 78 | /// in the handshake response. 79 | /// 80 | IgnoreExtension = 1010, 81 | /// 82 | /// Equivalent to close status 1011. 83 | /// Indicates that the server is terminating the connection because it has encountered 84 | /// an unexpected condition that prevented it from fulfilling the request. 85 | /// 86 | ServerError = 1011, 87 | /// 88 | /// Equivalent to close status 1015. 89 | /// Indicates that the connection was closed due to a failure to perform a TLS handshake. 90 | /// Reserved value. 91 | /// 92 | TlsHandshakeFailure = 1015 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /SocketHttpListener/CompressionMethod.cs: -------------------------------------------------------------------------------- 1 | namespace SocketHttpListener 2 | { 3 | /// 4 | /// Contains the values of the compression method used to compress the message on the WebSocket 5 | /// connection. 6 | /// 7 | /// 8 | /// The values of the compression method are defined in 9 | /// Compression 10 | /// Extensions for WebSocket. 11 | /// 12 | public enum CompressionMethod : byte 13 | { 14 | /// 15 | /// Indicates non compression. 16 | /// 17 | None, 18 | /// 19 | /// Indicates using DEFLATE. 20 | /// 21 | Deflate 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SocketHttpListener/ErrorEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SocketHttpListener 4 | { 5 | /// 6 | /// Contains the event data associated with a event. 7 | /// 8 | /// 9 | /// A event occurs when the gets an error. 10 | /// If you would like to get the error message, you should access the 11 | /// property. 12 | /// 13 | public class ErrorEventArgs : EventArgs 14 | { 15 | #region Private Fields 16 | 17 | private string _message; 18 | 19 | #endregion 20 | 21 | #region Internal Constructors 22 | 23 | internal ErrorEventArgs (string message) 24 | { 25 | _message = message; 26 | } 27 | 28 | #endregion 29 | 30 | #region Public Properties 31 | 32 | /// 33 | /// Gets the error message. 34 | /// 35 | /// 36 | /// A that represents the error message. 37 | /// 38 | public string Message { 39 | get { 40 | return _message; 41 | } 42 | } 43 | 44 | #endregion 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /SocketHttpListener/Fin.cs: -------------------------------------------------------------------------------- 1 | namespace SocketHttpListener 2 | { 3 | internal enum Fin : byte 4 | { 5 | More = 0x0, 6 | Final = 0x1 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /SocketHttpListener/HttpBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Specialized; 4 | using System.IO; 5 | using System.Text; 6 | using System.Threading; 7 | 8 | namespace SocketHttpListener 9 | { 10 | internal abstract class HttpBase 11 | { 12 | #region Private Fields 13 | 14 | private NameValueCollection _headers; 15 | private Version _version; 16 | 17 | #endregion 18 | 19 | #region Internal Fields 20 | 21 | internal byte[] EntityBodyData; 22 | 23 | #endregion 24 | 25 | #region Protected Fields 26 | 27 | protected const string CrLf = "\r\n"; 28 | 29 | #endregion 30 | 31 | #region Protected Constructors 32 | 33 | protected HttpBase(Version version, NameValueCollection headers) 34 | { 35 | _version = version; 36 | _headers = headers; 37 | } 38 | 39 | #endregion 40 | 41 | #region Public Properties 42 | 43 | public string EntityBody 44 | { 45 | get 46 | { 47 | return EntityBodyData != null && EntityBodyData.Length > 0 48 | ? getEncoding(_headers["Content-Type"]).GetString(EntityBodyData) 49 | : String.Empty; 50 | } 51 | } 52 | 53 | public NameValueCollection Headers 54 | { 55 | get 56 | { 57 | return _headers; 58 | } 59 | } 60 | 61 | public Version ProtocolVersion 62 | { 63 | get 64 | { 65 | return _version; 66 | } 67 | } 68 | 69 | #endregion 70 | 71 | #region Private Methods 72 | 73 | private static Encoding getEncoding(string contentType) 74 | { 75 | if (contentType == null || contentType.Length == 0) 76 | return Encoding.UTF8; 77 | 78 | var i = contentType.IndexOf("charset=", StringComparison.Ordinal); 79 | if (i == -1) 80 | return Encoding.UTF8; 81 | 82 | var charset = contentType.Substring(i + 8); 83 | i = charset.IndexOf(';'); 84 | if (i != -1) 85 | charset = charset.Substring(0, i).TrimEnd(); 86 | 87 | return Encoding.GetEncoding(charset.Trim('"')); 88 | } 89 | 90 | #endregion 91 | 92 | #region Public Methods 93 | 94 | public byte[] ToByteArray() 95 | { 96 | return Encoding.UTF8.GetBytes(ToString()); 97 | } 98 | 99 | #endregion 100 | } 101 | } -------------------------------------------------------------------------------- /SocketHttpListener/HttpResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Specialized; 3 | using System.IO; 4 | using System.Net; 5 | using System.Text; 6 | using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode; 7 | using HttpVersion = SocketHttpListener.Net.HttpVersion; 8 | using System.Linq; 9 | 10 | namespace SocketHttpListener 11 | { 12 | internal class HttpResponse : HttpBase 13 | { 14 | #region Private Fields 15 | 16 | private string _code; 17 | private string _reason; 18 | 19 | #endregion 20 | 21 | #region Private Constructors 22 | 23 | private HttpResponse(string code, string reason, Version version, NameValueCollection headers) 24 | : base(version, headers) 25 | { 26 | _code = code; 27 | _reason = reason; 28 | } 29 | 30 | #endregion 31 | 32 | #region Internal Constructors 33 | 34 | internal HttpResponse(HttpStatusCode code) 35 | : this(code, code.GetDescription()) 36 | { 37 | } 38 | 39 | internal HttpResponse(HttpStatusCode code, string reason) 40 | : this(((int)code).ToString(), reason, HttpVersion.Version11, new NameValueCollection()) 41 | { 42 | Headers["Server"] = "websocket-sharp/1.0"; 43 | } 44 | 45 | #endregion 46 | 47 | #region Public Properties 48 | 49 | public CookieCollection Cookies 50 | { 51 | get 52 | { 53 | return Headers.GetCookies(true); 54 | } 55 | } 56 | 57 | public bool IsProxyAuthenticationRequired 58 | { 59 | get 60 | { 61 | return _code == "407"; 62 | } 63 | } 64 | 65 | public bool IsUnauthorized 66 | { 67 | get 68 | { 69 | return _code == "401"; 70 | } 71 | } 72 | 73 | public bool IsWebSocketResponse 74 | { 75 | get 76 | { 77 | var headers = Headers; 78 | return ProtocolVersion > HttpVersion.Version10 && 79 | _code == "101" && 80 | headers.Contains("Upgrade", "websocket") && 81 | headers.Contains("Connection", "Upgrade"); 82 | } 83 | } 84 | 85 | public string Reason 86 | { 87 | get 88 | { 89 | return _reason; 90 | } 91 | } 92 | 93 | public string StatusCode 94 | { 95 | get 96 | { 97 | return _code; 98 | } 99 | } 100 | 101 | #endregion 102 | 103 | #region Internal Methods 104 | 105 | internal static HttpResponse CreateCloseResponse(HttpStatusCode code) 106 | { 107 | var res = new HttpResponse(code); 108 | res.Headers["Connection"] = "close"; 109 | 110 | return res; 111 | } 112 | 113 | internal static HttpResponse CreateWebSocketResponse() 114 | { 115 | var res = new HttpResponse(HttpStatusCode.SwitchingProtocols); 116 | 117 | var headers = res.Headers; 118 | headers["Upgrade"] = "websocket"; 119 | headers["Connection"] = "Upgrade"; 120 | 121 | return res; 122 | } 123 | 124 | #endregion 125 | 126 | #region Public Methods 127 | 128 | public void SetCookies(CookieCollection cookies) 129 | { 130 | if (cookies == null || cookies.Count == 0) 131 | return; 132 | 133 | var headers = Headers; 134 | var sorted = cookies.OfType().OrderBy(i => i.Name).ToList(); 135 | 136 | foreach (var cookie in sorted) 137 | headers.Add("Set-Cookie", cookie.ToString()); 138 | } 139 | 140 | public override string ToString() 141 | { 142 | var output = new StringBuilder(64); 143 | output.AppendFormat("HTTP/{0} {1} {2}{3}", ProtocolVersion, _code, _reason, CrLf); 144 | 145 | var headers = Headers; 146 | foreach (var key in headers.AllKeys) 147 | output.AppendFormat("{0}: {1}{2}", key, headers[key], CrLf); 148 | 149 | output.Append(CrLf); 150 | 151 | var entity = EntityBody; 152 | if (entity.Length > 0) 153 | output.Append(entity); 154 | 155 | return output.ToString(); 156 | } 157 | 158 | #endregion 159 | } 160 | } -------------------------------------------------------------------------------- /SocketHttpListener/Mask.cs: -------------------------------------------------------------------------------- 1 | namespace SocketHttpListener 2 | { 3 | internal enum Mask : byte 4 | { 5 | Unmask = 0x0, 6 | Mask = 0x1 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /SocketHttpListener/MessageEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace SocketHttpListener 5 | { 6 | /// 7 | /// Contains the event data associated with a event. 8 | /// 9 | /// 10 | /// A event occurs when the receives 11 | /// a text or binary data frame. 12 | /// If you want to get the received data, you access the or 13 | /// property. 14 | /// 15 | public class MessageEventArgs : EventArgs 16 | { 17 | #region Private Fields 18 | 19 | private string _data; 20 | private Opcode _opcode; 21 | private byte[] _rawData; 22 | 23 | #endregion 24 | 25 | #region Internal Constructors 26 | 27 | internal MessageEventArgs (Opcode opcode, byte[] data) 28 | { 29 | _opcode = opcode; 30 | _rawData = data; 31 | _data = convertToString (opcode, data); 32 | } 33 | 34 | internal MessageEventArgs (Opcode opcode, PayloadData payload) 35 | { 36 | _opcode = opcode; 37 | _rawData = payload.ApplicationData; 38 | _data = convertToString (opcode, _rawData); 39 | } 40 | 41 | #endregion 42 | 43 | #region Public Properties 44 | 45 | /// 46 | /// Gets the received data as a . 47 | /// 48 | /// 49 | /// A that contains the received data. 50 | /// 51 | public string Data { 52 | get { 53 | return _data; 54 | } 55 | } 56 | 57 | /// 58 | /// Gets the received data as an array of . 59 | /// 60 | /// 61 | /// An array of that contains the received data. 62 | /// 63 | public byte [] RawData { 64 | get { 65 | return _rawData; 66 | } 67 | } 68 | 69 | /// 70 | /// Gets the type of the received data. 71 | /// 72 | /// 73 | /// One of the values, indicates the type of the received data. 74 | /// 75 | public Opcode Type { 76 | get { 77 | return _opcode; 78 | } 79 | } 80 | 81 | #endregion 82 | 83 | #region Private Methods 84 | 85 | private static string convertToString (Opcode opcode, byte [] data) 86 | { 87 | return data.Length == 0 88 | ? String.Empty 89 | : opcode == Opcode.Text 90 | ? Encoding.UTF8.GetString (data, 0, data.Length) 91 | : opcode.ToString (); 92 | } 93 | 94 | #endregion 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /SocketHttpListener/Net/AuthenticationSchemeSelector.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace SocketHttpListener.Net 4 | { 5 | public delegate AuthenticationSchemes AuthenticationSchemeSelector(HttpListenerRequest httpRequest); 6 | } 7 | -------------------------------------------------------------------------------- /SocketHttpListener/Net/ChunkStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | using System.IO; 6 | using System.Net; 7 | using System.Text; 8 | 9 | namespace SocketHttpListener.Net 10 | { 11 | class ChunkStream 12 | { 13 | enum State 14 | { 15 | None, 16 | PartialSize, 17 | Body, 18 | BodyFinished, 19 | Trailer 20 | } 21 | 22 | class Chunk 23 | { 24 | public byte[] Bytes; 25 | public int Offset; 26 | 27 | public Chunk(byte[] chunk) 28 | { 29 | this.Bytes = chunk; 30 | } 31 | 32 | public int Read(byte[] buffer, int offset, int size) 33 | { 34 | int nread = (size > Bytes.Length - Offset) ? Bytes.Length - Offset : size; 35 | Buffer.BlockCopy(Bytes, Offset, buffer, offset, nread); 36 | Offset += nread; 37 | return nread; 38 | } 39 | } 40 | 41 | internal WebHeaderCollection headers; 42 | int chunkSize; 43 | int chunkRead; 44 | int totalWritten; 45 | State state; 46 | //byte [] waitBuffer; 47 | StringBuilder saved; 48 | bool sawCR; 49 | bool gotit; 50 | int trailerState; 51 | List chunks; 52 | 53 | public ChunkStream(byte[] buffer, int offset, int size, WebHeaderCollection headers) 54 | : this(headers) 55 | { 56 | Write(buffer, offset, size); 57 | } 58 | 59 | public ChunkStream(WebHeaderCollection headers) 60 | { 61 | this.headers = headers; 62 | saved = new StringBuilder(); 63 | chunks = new List(); 64 | chunkSize = -1; 65 | totalWritten = 0; 66 | } 67 | 68 | public void ResetBuffer() 69 | { 70 | chunkSize = -1; 71 | chunkRead = 0; 72 | totalWritten = 0; 73 | chunks.Clear(); 74 | } 75 | 76 | public void WriteAndReadBack(byte[] buffer, int offset, int size, ref int read) 77 | { 78 | if (offset + read > 0) 79 | Write(buffer, offset, offset + read); 80 | read = Read(buffer, offset, size); 81 | } 82 | 83 | public int Read(byte[] buffer, int offset, int size) 84 | { 85 | return ReadFromChunks(buffer, offset, size); 86 | } 87 | 88 | int ReadFromChunks(byte[] buffer, int offset, int size) 89 | { 90 | int count = chunks.Count; 91 | int nread = 0; 92 | 93 | var chunksForRemoving = new List(count); 94 | for (int i = 0; i < count; i++) 95 | { 96 | Chunk chunk = (Chunk)chunks[i]; 97 | 98 | if (chunk.Offset == chunk.Bytes.Length) 99 | { 100 | chunksForRemoving.Add(chunk); 101 | continue; 102 | } 103 | 104 | nread += chunk.Read(buffer, offset + nread, size - nread); 105 | if (nread == size) 106 | break; 107 | } 108 | 109 | foreach (var chunk in chunksForRemoving) 110 | chunks.Remove(chunk); 111 | 112 | return nread; 113 | } 114 | 115 | public void Write(byte[] buffer, int offset, int size) 116 | { 117 | if (offset < size) 118 | InternalWrite(buffer, ref offset, size); 119 | } 120 | 121 | void InternalWrite(byte[] buffer, ref int offset, int size) 122 | { 123 | if (state == State.None || state == State.PartialSize) 124 | { 125 | state = GetChunkSize(buffer, ref offset, size); 126 | if (state == State.PartialSize) 127 | return; 128 | 129 | saved.Length = 0; 130 | sawCR = false; 131 | gotit = false; 132 | } 133 | 134 | if (state == State.Body && offset < size) 135 | { 136 | state = ReadBody(buffer, ref offset, size); 137 | if (state == State.Body) 138 | return; 139 | } 140 | 141 | if (state == State.BodyFinished && offset < size) 142 | { 143 | state = ReadCRLF(buffer, ref offset, size); 144 | if (state == State.BodyFinished) 145 | return; 146 | 147 | sawCR = false; 148 | } 149 | 150 | if (state == State.Trailer && offset < size) 151 | { 152 | state = ReadTrailer(buffer, ref offset, size); 153 | if (state == State.Trailer) 154 | return; 155 | 156 | saved.Length = 0; 157 | sawCR = false; 158 | gotit = false; 159 | } 160 | 161 | if (offset < size) 162 | InternalWrite(buffer, ref offset, size); 163 | } 164 | 165 | public bool WantMore 166 | { 167 | get { return (chunkRead != chunkSize || chunkSize != 0 || state != State.None); } 168 | } 169 | 170 | public bool DataAvailable 171 | { 172 | get 173 | { 174 | int count = chunks.Count; 175 | for (int i = 0; i < count; i++) 176 | { 177 | Chunk ch = (Chunk)chunks[i]; 178 | if (ch == null || ch.Bytes == null) 179 | continue; 180 | if (ch.Bytes.Length > 0 && ch.Offset < ch.Bytes.Length) 181 | return (state != State.Body); 182 | } 183 | return false; 184 | } 185 | } 186 | 187 | public int TotalDataSize 188 | { 189 | get { return totalWritten; } 190 | } 191 | 192 | public int ChunkLeft 193 | { 194 | get { return chunkSize - chunkRead; } 195 | } 196 | 197 | State ReadBody(byte[] buffer, ref int offset, int size) 198 | { 199 | if (chunkSize == 0) 200 | return State.BodyFinished; 201 | 202 | int diff = size - offset; 203 | if (diff + chunkRead > chunkSize) 204 | diff = chunkSize - chunkRead; 205 | 206 | byte[] chunk = new byte[diff]; 207 | Buffer.BlockCopy(buffer, offset, chunk, 0, diff); 208 | chunks.Add(new Chunk(chunk)); 209 | offset += diff; 210 | chunkRead += diff; 211 | totalWritten += diff; 212 | return (chunkRead == chunkSize) ? State.BodyFinished : State.Body; 213 | 214 | } 215 | 216 | State GetChunkSize(byte[] buffer, ref int offset, int size) 217 | { 218 | chunkRead = 0; 219 | chunkSize = 0; 220 | char c = '\0'; 221 | while (offset < size) 222 | { 223 | c = (char)buffer[offset++]; 224 | if (c == '\r') 225 | { 226 | if (sawCR) 227 | ThrowProtocolViolation("2 CR found"); 228 | 229 | sawCR = true; 230 | continue; 231 | } 232 | 233 | if (sawCR && c == '\n') 234 | break; 235 | 236 | if (c == ' ') 237 | gotit = true; 238 | 239 | if (!gotit) 240 | saved.Append(c); 241 | 242 | if (saved.Length > 20) 243 | ThrowProtocolViolation("chunk size too long."); 244 | } 245 | 246 | if (!sawCR || c != '\n') 247 | { 248 | if (offset < size) 249 | ThrowProtocolViolation("Missing \\n"); 250 | 251 | try 252 | { 253 | if (saved.Length > 0) 254 | { 255 | chunkSize = Int32.Parse(RemoveChunkExtension(saved.ToString()), NumberStyles.HexNumber); 256 | } 257 | } 258 | catch (Exception) 259 | { 260 | ThrowProtocolViolation("Cannot parse chunk size."); 261 | } 262 | 263 | return State.PartialSize; 264 | } 265 | 266 | chunkRead = 0; 267 | try 268 | { 269 | chunkSize = Int32.Parse(RemoveChunkExtension(saved.ToString()), NumberStyles.HexNumber); 270 | } 271 | catch (Exception) 272 | { 273 | ThrowProtocolViolation("Cannot parse chunk size."); 274 | } 275 | 276 | if (chunkSize == 0) 277 | { 278 | trailerState = 2; 279 | return State.Trailer; 280 | } 281 | 282 | return State.Body; 283 | } 284 | 285 | static string RemoveChunkExtension(string input) 286 | { 287 | int idx = input.IndexOf(';'); 288 | if (idx == -1) 289 | return input; 290 | return input.Substring(0, idx); 291 | } 292 | 293 | State ReadCRLF(byte[] buffer, ref int offset, int size) 294 | { 295 | if (!sawCR) 296 | { 297 | if ((char)buffer[offset++] != '\r') 298 | ThrowProtocolViolation("Expecting \\r"); 299 | 300 | sawCR = true; 301 | if (offset == size) 302 | return State.BodyFinished; 303 | } 304 | 305 | if (sawCR && (char)buffer[offset++] != '\n') 306 | ThrowProtocolViolation("Expecting \\n"); 307 | 308 | return State.None; 309 | } 310 | 311 | State ReadTrailer(byte[] buffer, ref int offset, int size) 312 | { 313 | char c = '\0'; 314 | 315 | // short path 316 | if (trailerState == 2 && (char)buffer[offset] == '\r' && saved.Length == 0) 317 | { 318 | offset++; 319 | if (offset < size && (char)buffer[offset] == '\n') 320 | { 321 | offset++; 322 | return State.None; 323 | } 324 | offset--; 325 | } 326 | 327 | int st = trailerState; 328 | string stString = "\r\n\r"; 329 | while (offset < size && st < 4) 330 | { 331 | c = (char)buffer[offset++]; 332 | if ((st == 0 || st == 2) && c == '\r') 333 | { 334 | st++; 335 | continue; 336 | } 337 | 338 | if ((st == 1 || st == 3) && c == '\n') 339 | { 340 | st++; 341 | continue; 342 | } 343 | 344 | if (st > 0) 345 | { 346 | saved.Append(stString.Substring(0, saved.Length == 0 ? st - 2 : st)); 347 | st = 0; 348 | if (saved.Length > 4196) 349 | ThrowProtocolViolation("Error reading trailer (too long)."); 350 | } 351 | } 352 | 353 | if (st < 4) 354 | { 355 | trailerState = st; 356 | if (offset < size) 357 | ThrowProtocolViolation("Error reading trailer."); 358 | 359 | return State.Trailer; 360 | } 361 | 362 | StringReader reader = new StringReader(saved.ToString()); 363 | string line; 364 | while ((line = reader.ReadLine()) != null && line != "") 365 | headers.Add(line); 366 | 367 | return State.None; 368 | } 369 | 370 | static void ThrowProtocolViolation(string message) 371 | { 372 | WebException we = new WebException(message, null, WebExceptionStatus.UnknownError, null); 373 | //WebException we = new WebException(message, null, WebExceptionStatus.ServerProtocolViolation, null); 374 | throw we; 375 | } 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /SocketHttpListener/Net/ChunkedInputStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace SocketHttpListener.Net 6 | { 7 | class ChunkedInputStream : RequestStream 8 | { 9 | bool disposed; 10 | ChunkStream decoder; 11 | HttpListenerContext context; 12 | bool no_more_data; 13 | 14 | class ReadBufferState 15 | { 16 | public byte[] Buffer; 17 | public int Offset; 18 | public int Count; 19 | public int InitialCount; 20 | public HttpStreamAsyncResult Ares; 21 | public ReadBufferState(byte[] buffer, int offset, int count, 22 | HttpStreamAsyncResult ares) 23 | { 24 | Buffer = buffer; 25 | Offset = offset; 26 | Count = count; 27 | InitialCount = count; 28 | Ares = ares; 29 | } 30 | } 31 | 32 | public ChunkedInputStream(HttpListenerContext context, Stream stream, 33 | byte[] buffer, int offset, int length) 34 | : base(stream, buffer, offset, length) 35 | { 36 | this.context = context; 37 | WebHeaderCollection coll = (WebHeaderCollection)context.Request.Headers; 38 | decoder = new ChunkStream(coll); 39 | } 40 | 41 | public ChunkStream Decoder 42 | { 43 | get { return decoder; } 44 | set { decoder = value; } 45 | } 46 | 47 | public override int Read([In, Out] byte[] buffer, int offset, int count) 48 | { 49 | IAsyncResult ares = BeginRead(buffer, offset, count, null, null); 50 | return EndRead(ares); 51 | } 52 | 53 | public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, 54 | AsyncCallback cback, object state) 55 | { 56 | if (disposed) 57 | throw new ObjectDisposedException(GetType().ToString()); 58 | 59 | if (buffer == null) 60 | throw new ArgumentNullException("buffer"); 61 | 62 | int len = buffer.Length; 63 | if (offset < 0 || offset > len) 64 | throw new ArgumentOutOfRangeException("offset exceeds the size of buffer"); 65 | 66 | if (count < 0 || offset > len - count) 67 | throw new ArgumentOutOfRangeException("offset+size exceeds the size of buffer"); 68 | 69 | HttpStreamAsyncResult ares = new HttpStreamAsyncResult(); 70 | ares.Callback = cback; 71 | ares.State = state; 72 | if (no_more_data) 73 | { 74 | ares.Complete(); 75 | return ares; 76 | } 77 | int nread = decoder.Read(buffer, offset, count); 78 | offset += nread; 79 | count -= nread; 80 | if (count == 0) 81 | { 82 | // got all we wanted, no need to bother the decoder yet 83 | ares.Count = nread; 84 | ares.Complete(); 85 | return ares; 86 | } 87 | if (!decoder.WantMore) 88 | { 89 | no_more_data = nread == 0; 90 | ares.Count = nread; 91 | ares.Complete(); 92 | return ares; 93 | } 94 | ares.Buffer = new byte[8192]; 95 | ares.Offset = 0; 96 | ares.Count = 8192; 97 | ReadBufferState rb = new ReadBufferState(buffer, offset, count, ares); 98 | rb.InitialCount += nread; 99 | base.BeginRead(ares.Buffer, ares.Offset, ares.Count, OnRead, rb); 100 | return ares; 101 | } 102 | 103 | void OnRead(IAsyncResult base_ares) 104 | { 105 | ReadBufferState rb = (ReadBufferState)base_ares.AsyncState; 106 | HttpStreamAsyncResult ares = rb.Ares; 107 | try 108 | { 109 | int nread = base.EndRead(base_ares); 110 | decoder.Write(ares.Buffer, ares.Offset, nread); 111 | nread = decoder.Read(rb.Buffer, rb.Offset, rb.Count); 112 | rb.Offset += nread; 113 | rb.Count -= nread; 114 | if (rb.Count == 0 || !decoder.WantMore || nread == 0) 115 | { 116 | no_more_data = !decoder.WantMore && nread == 0; 117 | ares.Count = rb.InitialCount - rb.Count; 118 | ares.Complete(); 119 | return; 120 | } 121 | ares.Offset = 0; 122 | ares.Count = Math.Min(8192, decoder.ChunkLeft + 6); 123 | base.BeginRead(ares.Buffer, ares.Offset, ares.Count, OnRead, rb); 124 | } 125 | catch (Exception e) 126 | { 127 | context.Connection.SendError(e.Message, 400); 128 | ares.Complete(e); 129 | } 130 | } 131 | 132 | public override int EndRead(IAsyncResult ares) 133 | { 134 | if (disposed) 135 | throw new ObjectDisposedException(GetType().ToString()); 136 | 137 | HttpStreamAsyncResult my_ares = ares as HttpStreamAsyncResult; 138 | if (ares == null) 139 | throw new ArgumentException("Invalid IAsyncResult", "ares"); 140 | 141 | if (!ares.IsCompleted) 142 | ares.AsyncWaitHandle.WaitOne(); 143 | 144 | if (my_ares.Error != null) 145 | throw new System.Net.HttpListenerException(400, "I/O operation aborted: " + my_ares.Error.Message); 146 | 147 | return my_ares.Count; 148 | } 149 | 150 | public override void Close() 151 | { 152 | if (!disposed) 153 | { 154 | disposed = true; 155 | base.Close(); 156 | } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /SocketHttpListener/Net/CookieHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SocketHttpListener.Net 10 | { 11 | public static class CookieHelper 12 | { 13 | internal static CookieCollection Parse(string value, bool response) 14 | { 15 | return response 16 | ? parseResponse(value) 17 | : null; 18 | } 19 | 20 | private static string[] splitCookieHeaderValue(string value) 21 | { 22 | return new List(value.SplitHeaderValue(',', ';')).ToArray(); 23 | } 24 | 25 | private static CookieCollection parseResponse(string value) 26 | { 27 | var cookies = new CookieCollection(); 28 | 29 | Cookie cookie = null; 30 | var pairs = splitCookieHeaderValue(value); 31 | for (int i = 0; i < pairs.Length; i++) 32 | { 33 | var pair = pairs[i].Trim(); 34 | if (pair.Length == 0) 35 | continue; 36 | 37 | if (pair.StartsWith("version", StringComparison.OrdinalIgnoreCase)) 38 | { 39 | if (cookie != null) 40 | cookie.Version = Int32.Parse(pair.GetValueInternal("=").Trim('"')); 41 | } 42 | else if (pair.StartsWith("expires", StringComparison.OrdinalIgnoreCase)) 43 | { 44 | var buffer = new StringBuilder(pair.GetValueInternal("="), 32); 45 | if (i < pairs.Length - 1) 46 | buffer.AppendFormat(", {0}", pairs[++i].Trim()); 47 | 48 | DateTime expires; 49 | if (!DateTime.TryParseExact( 50 | buffer.ToString(), 51 | new[] { "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", "r" }, 52 | new CultureInfo("en-US"), 53 | DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, 54 | out expires)) 55 | expires = DateTime.Now; 56 | 57 | if (cookie != null && cookie.Expires == DateTime.MinValue) 58 | cookie.Expires = expires.ToLocalTime(); 59 | } 60 | else if (pair.StartsWith("max-age", StringComparison.OrdinalIgnoreCase)) 61 | { 62 | var max = Int32.Parse(pair.GetValueInternal("=").Trim('"')); 63 | var expires = DateTime.Now.AddSeconds((double)max); 64 | if (cookie != null) 65 | cookie.Expires = expires; 66 | } 67 | else if (pair.StartsWith("path", StringComparison.OrdinalIgnoreCase)) 68 | { 69 | if (cookie != null) 70 | cookie.Path = pair.GetValueInternal("="); 71 | } 72 | else if (pair.StartsWith("domain", StringComparison.OrdinalIgnoreCase)) 73 | { 74 | if (cookie != null) 75 | cookie.Domain = pair.GetValueInternal("="); 76 | } 77 | else if (pair.StartsWith("port", StringComparison.OrdinalIgnoreCase)) 78 | { 79 | var port = pair.Equals("port", StringComparison.OrdinalIgnoreCase) 80 | ? "\"\"" 81 | : pair.GetValueInternal("="); 82 | 83 | if (cookie != null) 84 | cookie.Port = port; 85 | } 86 | else if (pair.StartsWith("comment", StringComparison.OrdinalIgnoreCase)) 87 | { 88 | if (cookie != null) 89 | cookie.Comment = pair.GetValueInternal("=").UrlDecode(); 90 | } 91 | else if (pair.StartsWith("commenturl", StringComparison.OrdinalIgnoreCase)) 92 | { 93 | if (cookie != null) 94 | cookie.CommentUri = pair.GetValueInternal("=").Trim('"').ToUri(); 95 | } 96 | else if (pair.StartsWith("discard", StringComparison.OrdinalIgnoreCase)) 97 | { 98 | if (cookie != null) 99 | cookie.Discard = true; 100 | } 101 | else if (pair.StartsWith("secure", StringComparison.OrdinalIgnoreCase)) 102 | { 103 | if (cookie != null) 104 | cookie.Secure = true; 105 | } 106 | else if (pair.StartsWith("httponly", StringComparison.OrdinalIgnoreCase)) 107 | { 108 | if (cookie != null) 109 | cookie.HttpOnly = true; 110 | } 111 | else 112 | { 113 | if (cookie != null) 114 | cookies.Add(cookie); 115 | 116 | string name; 117 | string val = String.Empty; 118 | 119 | var pos = pair.IndexOf('='); 120 | if (pos == -1) 121 | { 122 | name = pair; 123 | } 124 | else if (pos == pair.Length - 1) 125 | { 126 | name = pair.Substring(0, pos).TrimEnd(' '); 127 | } 128 | else 129 | { 130 | name = pair.Substring(0, pos).TrimEnd(' '); 131 | val = pair.Substring(pos + 1).TrimStart(' '); 132 | } 133 | 134 | cookie = new Cookie(name, val); 135 | } 136 | } 137 | 138 | if (cookie != null) 139 | cookies.Add(cookie); 140 | 141 | return cookies; 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /SocketHttpListener/Net/EndPointListener.cs: -------------------------------------------------------------------------------- 1 | using Patterns.Logging; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Net; 7 | using System.Net.Sockets; 8 | using System.Security.Cryptography.X509Certificates; 9 | using System.Threading; 10 | 11 | namespace SocketHttpListener.Net 12 | { 13 | sealed class EndPointListener 14 | { 15 | HttpListener listener; 16 | IPEndPoint endpoint; 17 | Socket sock; 18 | Dictionary prefixes; // Dictionary 19 | List unhandled; // List unhandled; host = '*' 20 | List all; // List all; host = '+' 21 | X509Certificate cert; 22 | bool secure; 23 | Dictionary unregistered; 24 | private readonly ILogger _logger; 25 | private bool _closed; 26 | private readonly bool _enableDualMode; 27 | 28 | public EndPointListener(HttpListener listener, IPAddress addr, int port, bool secure, X509Certificate cert, ILogger logger) 29 | { 30 | this.listener = listener; 31 | _logger = logger; 32 | 33 | this.secure = secure; 34 | this.cert = cert; 35 | 36 | _enableDualMode = Equals(addr, IPAddress.IPv6Any); 37 | endpoint = new IPEndPoint(addr, port); 38 | 39 | prefixes = new Dictionary(); 40 | unregistered = new Dictionary(); 41 | 42 | CreateSocket(); 43 | } 44 | 45 | internal HttpListener Listener 46 | { 47 | get 48 | { 49 | return listener; 50 | } 51 | } 52 | 53 | private void CreateSocket() 54 | { 55 | if (_enableDualMode) 56 | { 57 | _logger.Info("Enabling DualMode socket"); 58 | 59 | if (Environment.OSVersion.Platform == PlatformID.Win32NT) 60 | { 61 | sock = new Socket(endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); 62 | EnableDualMode(sock); 63 | } 64 | else 65 | { 66 | sock = new Socket(SocketType.Stream, ProtocolType.Tcp); 67 | } 68 | } 69 | else 70 | { 71 | _logger.Info("Enabling non-DualMode socket"); 72 | sock = new Socket(endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); 73 | } 74 | 75 | sock.Bind(endpoint); 76 | 77 | // This is the number TcpListener uses. 78 | sock.Listen(2147483647); 79 | 80 | Socket dummy = null; 81 | StartAccept(null, ref dummy); 82 | _closed = false; 83 | } 84 | 85 | private void EnableDualMode(Socket socket) 86 | { 87 | try 88 | { 89 | //sock.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false); 90 | 91 | socket.DualMode = true; 92 | } 93 | catch (MissingMemberException) 94 | { 95 | } 96 | } 97 | 98 | public void StartAccept(SocketAsyncEventArgs acceptEventArg, ref Socket accepted) 99 | { 100 | if (acceptEventArg == null) 101 | { 102 | acceptEventArg = new SocketAsyncEventArgs(); 103 | acceptEventArg.Completed += new EventHandler(AcceptEventArg_Completed); 104 | } 105 | else 106 | { 107 | // socket must be cleared since the context object is being reused 108 | acceptEventArg.AcceptSocket = null; 109 | } 110 | 111 | try 112 | { 113 | bool willRaiseEvent = sock.AcceptAsync(acceptEventArg); 114 | 115 | if (!willRaiseEvent) 116 | { 117 | ProcessAccept(acceptEventArg); 118 | } 119 | } 120 | catch 121 | { 122 | if (accepted != null) 123 | { 124 | try 125 | { 126 | accepted.Close(); 127 | } 128 | catch 129 | { 130 | } 131 | accepted = null; 132 | } 133 | } 134 | } 135 | 136 | // This method is the callback method associated with Socket.AcceptAsync 137 | // operations and is invoked when an accept operation is complete 138 | // 139 | void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e) 140 | { 141 | ProcessAccept(e); 142 | } 143 | 144 | private void ProcessAccept(SocketAsyncEventArgs e) 145 | { 146 | if (_closed) 147 | { 148 | return; 149 | } 150 | 151 | // http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.acceptasync%28v=vs.110%29.aspx 152 | // Under certain conditions ConnectionReset can occur 153 | // Need to attept to re-accept 154 | if (e.SocketError == SocketError.ConnectionReset) 155 | { 156 | _logger.Error("SocketError.ConnectionReset reported. Attempting to re-accept."); 157 | Socket dummy = null; 158 | StartAccept(e, ref dummy); 159 | return; 160 | } 161 | 162 | var acceptSocket = e.AcceptSocket; 163 | if (acceptSocket != null) 164 | { 165 | ProcessAccept(acceptSocket); 166 | } 167 | 168 | if (sock != null) 169 | { 170 | // Accept the next connection request 171 | StartAccept(e, ref acceptSocket); 172 | } 173 | } 174 | 175 | private void ProcessAccept(Socket accepted) 176 | { 177 | try 178 | { 179 | var listener = this; 180 | 181 | if (listener.secure && listener.cert == null) 182 | { 183 | accepted.Close(); 184 | return; 185 | } 186 | 187 | HttpConnection conn = new HttpConnection(_logger, accepted, listener, listener.secure, listener.cert); 188 | //_logger.Debug("Adding unregistered connection to {0}. Id: {1}", accepted.RemoteEndPoint, connectionId); 189 | lock (listener.unregistered) 190 | { 191 | listener.unregistered[conn] = conn; 192 | } 193 | conn.BeginReadRequest(); 194 | } 195 | catch (Exception ex) 196 | { 197 | _logger.ErrorException("Error in ProcessAccept", ex); 198 | } 199 | } 200 | 201 | internal void RemoveConnection(HttpConnection conn) 202 | { 203 | lock (unregistered) 204 | { 205 | unregistered.Remove(conn); 206 | } 207 | } 208 | 209 | public bool BindContext(HttpListenerContext context) 210 | { 211 | HttpListenerRequest req = context.Request; 212 | ListenerPrefix prefix; 213 | HttpListener listener = SearchListener(req.Url, out prefix); 214 | if (listener == null) 215 | return false; 216 | 217 | context.Listener = listener; 218 | context.Connection.Prefix = prefix; 219 | return true; 220 | } 221 | 222 | public void UnbindContext(HttpListenerContext context) 223 | { 224 | if (context == null || context.Request == null) 225 | return; 226 | 227 | context.Listener.UnregisterContext(context); 228 | } 229 | 230 | HttpListener SearchListener(Uri uri, out ListenerPrefix prefix) 231 | { 232 | prefix = null; 233 | if (uri == null) 234 | return null; 235 | 236 | string host = uri.Host; 237 | int port = uri.Port; 238 | string path = WebUtility.UrlDecode(uri.AbsolutePath); 239 | string path_slash = path[path.Length - 1] == '/' ? path : path + "/"; 240 | 241 | HttpListener best_match = null; 242 | int best_length = -1; 243 | 244 | if (host != null && host != "") 245 | { 246 | var p_ro = prefixes; 247 | foreach (ListenerPrefix p in p_ro.Keys) 248 | { 249 | string ppath = p.Path; 250 | if (ppath.Length < best_length) 251 | continue; 252 | 253 | if (p.Host != host || p.Port != port) 254 | continue; 255 | 256 | if (path.StartsWith(ppath) || path_slash.StartsWith(ppath)) 257 | { 258 | best_length = ppath.Length; 259 | best_match = (HttpListener)p_ro[p]; 260 | prefix = p; 261 | } 262 | } 263 | if (best_length != -1) 264 | return best_match; 265 | } 266 | 267 | List list = unhandled; 268 | best_match = MatchFromList(host, path, list, out prefix); 269 | if (path != path_slash && best_match == null) 270 | best_match = MatchFromList(host, path_slash, list, out prefix); 271 | if (best_match != null) 272 | return best_match; 273 | 274 | list = all; 275 | best_match = MatchFromList(host, path, list, out prefix); 276 | if (path != path_slash && best_match == null) 277 | best_match = MatchFromList(host, path_slash, list, out prefix); 278 | if (best_match != null) 279 | return best_match; 280 | 281 | return null; 282 | } 283 | 284 | HttpListener MatchFromList(string host, string path, List list, out ListenerPrefix prefix) 285 | { 286 | prefix = null; 287 | if (list == null) 288 | return null; 289 | 290 | HttpListener best_match = null; 291 | int best_length = -1; 292 | 293 | foreach (ListenerPrefix p in list) 294 | { 295 | string ppath = p.Path; 296 | if (ppath.Length < best_length) 297 | continue; 298 | 299 | if (path.StartsWith(ppath)) 300 | { 301 | best_length = ppath.Length; 302 | best_match = p.Listener; 303 | prefix = p; 304 | } 305 | } 306 | 307 | return best_match; 308 | } 309 | 310 | void AddSpecial(List coll, ListenerPrefix prefix) 311 | { 312 | if (coll == null) 313 | return; 314 | 315 | foreach (ListenerPrefix p in coll) 316 | { 317 | if (p.Path == prefix.Path) //TODO: code 318 | throw new System.Net.HttpListenerException(400, "Prefix already in use."); 319 | } 320 | coll.Add(prefix); 321 | } 322 | 323 | bool RemoveSpecial(List coll, ListenerPrefix prefix) 324 | { 325 | if (coll == null) 326 | return false; 327 | 328 | int c = coll.Count; 329 | for (int i = 0; i < c; i++) 330 | { 331 | ListenerPrefix p = (ListenerPrefix)coll[i]; 332 | if (p.Path == prefix.Path) 333 | { 334 | coll.RemoveAt(i); 335 | return true; 336 | } 337 | } 338 | return false; 339 | } 340 | 341 | void CheckIfRemove() 342 | { 343 | if (prefixes.Count > 0) 344 | return; 345 | 346 | List list = unhandled; 347 | if (list != null && list.Count > 0) 348 | return; 349 | 350 | list = all; 351 | if (list != null && list.Count > 0) 352 | return; 353 | 354 | EndPointManager.RemoveEndPoint(this, endpoint); 355 | } 356 | 357 | public void Close() 358 | { 359 | _closed = true; 360 | sock.Close(); 361 | lock (unregistered) 362 | { 363 | // 364 | // Clone the list because RemoveConnection can be called from Close 365 | // 366 | var connections = new List(unregistered.Keys); 367 | 368 | foreach (HttpConnection c in connections) 369 | c.Close(true); 370 | unregistered.Clear(); 371 | } 372 | } 373 | 374 | public void AddPrefix(ListenerPrefix prefix, HttpListener listener) 375 | { 376 | List current; 377 | List future; 378 | if (prefix.Host == "*") 379 | { 380 | do 381 | { 382 | current = unhandled; 383 | future = (current != null) ? current.ToList() : new List(); 384 | prefix.Listener = listener; 385 | AddSpecial(future, prefix); 386 | } while (Interlocked.CompareExchange(ref unhandled, future, current) != current); 387 | return; 388 | } 389 | 390 | if (prefix.Host == "+") 391 | { 392 | do 393 | { 394 | current = all; 395 | future = (current != null) ? current.ToList() : new List(); 396 | prefix.Listener = listener; 397 | AddSpecial(future, prefix); 398 | } while (Interlocked.CompareExchange(ref all, future, current) != current); 399 | return; 400 | } 401 | 402 | Dictionary prefs; 403 | Dictionary p2; 404 | do 405 | { 406 | prefs = prefixes; 407 | if (prefs.ContainsKey(prefix)) 408 | { 409 | HttpListener other = (HttpListener)prefs[prefix]; 410 | if (other != listener) // TODO: code. 411 | throw new System.Net.HttpListenerException(400, "There's another listener for " + prefix); 412 | return; 413 | } 414 | p2 = new Dictionary(prefs); 415 | p2[prefix] = listener; 416 | } while (Interlocked.CompareExchange(ref prefixes, p2, prefs) != prefs); 417 | } 418 | 419 | public void RemovePrefix(ListenerPrefix prefix, HttpListener listener) 420 | { 421 | List current; 422 | List future; 423 | if (prefix.Host == "*") 424 | { 425 | do 426 | { 427 | current = unhandled; 428 | future = (current != null) ? current.ToList() : new List(); 429 | if (!RemoveSpecial(future, prefix)) 430 | break; // Prefix not found 431 | } while (Interlocked.CompareExchange(ref unhandled, future, current) != current); 432 | CheckIfRemove(); 433 | return; 434 | } 435 | 436 | if (prefix.Host == "+") 437 | { 438 | do 439 | { 440 | current = all; 441 | future = (current != null) ? current.ToList() : new List(); 442 | if (!RemoveSpecial(future, prefix)) 443 | break; // Prefix not found 444 | } while (Interlocked.CompareExchange(ref all, future, current) != current); 445 | CheckIfRemove(); 446 | return; 447 | } 448 | 449 | Dictionary prefs; 450 | Dictionary p2; 451 | do 452 | { 453 | prefs = prefixes; 454 | if (!prefs.ContainsKey(prefix)) 455 | break; 456 | 457 | p2 = new Dictionary(prefs); 458 | p2.Remove(prefix); 459 | } while (Interlocked.CompareExchange(ref prefixes, p2, prefs) != prefs); 460 | CheckIfRemove(); 461 | } 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /SocketHttpListener/Net/EndPointManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Reflection; 7 | using Patterns.Logging; 8 | 9 | namespace SocketHttpListener.Net 10 | { 11 | sealed class EndPointManager 12 | { 13 | // Dictionary> 14 | static Dictionary> ip_to_endpoints = new Dictionary>(); 15 | 16 | private EndPointManager() 17 | { 18 | } 19 | 20 | public static void AddListener(ILogger logger, HttpListener listener) 21 | { 22 | List added = new List(); 23 | try 24 | { 25 | lock (ip_to_endpoints) 26 | { 27 | foreach (string prefix in listener.Prefixes) 28 | { 29 | AddPrefixInternal(logger, prefix, listener); 30 | added.Add(prefix); 31 | } 32 | } 33 | } 34 | catch 35 | { 36 | foreach (string prefix in added) 37 | { 38 | RemovePrefix(logger, prefix, listener); 39 | } 40 | throw; 41 | } 42 | } 43 | 44 | public static void AddPrefix(ILogger logger, string prefix, HttpListener listener) 45 | { 46 | lock (ip_to_endpoints) 47 | { 48 | AddPrefixInternal(logger, prefix, listener); 49 | } 50 | } 51 | 52 | static void AddPrefixInternal(ILogger logger, string p, HttpListener listener) 53 | { 54 | ListenerPrefix lp = new ListenerPrefix(p); 55 | if (lp.Path.IndexOf('%') != -1) 56 | throw new System.Net.HttpListenerException(400, "Invalid path."); 57 | 58 | if (lp.Path.IndexOf("//", StringComparison.Ordinal) != -1) // TODO: Code? 59 | throw new System.Net.HttpListenerException(400, "Invalid path."); 60 | 61 | // listens on all the interfaces if host name cannot be parsed by IPAddress. 62 | EndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure); 63 | epl.AddPrefix(lp, listener); 64 | } 65 | 66 | private static bool SupportsDualMode() 67 | { 68 | if (Environment.OSVersion.Platform == PlatformID.Win32NT) 69 | { 70 | return true; 71 | } 72 | 73 | return false; 74 | //return GetMonoVersion() >= new Version(4, 4); 75 | } 76 | 77 | private static Version GetMonoVersion() 78 | { 79 | Type type = Type.GetType("Mono.Runtime"); 80 | if (type != null) 81 | { 82 | MethodInfo displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static); 83 | var displayNameValue = displayName.Invoke(null, null).ToString().Trim().Split(' ')[0]; 84 | 85 | Version version; 86 | if (Version.TryParse(displayNameValue, out version)) 87 | { 88 | return version; 89 | } 90 | } 91 | 92 | return new Version(1, 0); 93 | } 94 | 95 | private static IPAddress GetIpAnyAddress() 96 | { 97 | return SupportsDualMode() ? IPAddress.IPv6Any : IPAddress.Any; 98 | } 99 | 100 | static EndPointListener GetEPListener(ILogger logger, string host, int port, HttpListener listener, bool secure) 101 | { 102 | IPAddress addr; 103 | if (host == "*" || host == "+") 104 | addr = GetIpAnyAddress(); 105 | else if (IPAddress.TryParse(host, out addr) == false) 106 | { 107 | try 108 | { 109 | IPHostEntry iphost = Dns.GetHostByName(host); 110 | if (iphost != null) 111 | addr = iphost.AddressList[0]; 112 | else 113 | addr = GetIpAnyAddress(); 114 | } 115 | catch 116 | { 117 | addr = GetIpAnyAddress(); 118 | } 119 | } 120 | Dictionary p = null; // Dictionary 121 | if (!ip_to_endpoints.TryGetValue(addr, out p)) 122 | { 123 | p = new Dictionary(); 124 | ip_to_endpoints[addr] = p; 125 | } 126 | 127 | EndPointListener epl = null; 128 | if (p.ContainsKey(port)) 129 | { 130 | epl = (EndPointListener)p[port]; 131 | } 132 | else 133 | { 134 | epl = new EndPointListener(listener, addr, port, secure, listener.Certificate, logger); 135 | p[port] = epl; 136 | } 137 | 138 | return epl; 139 | } 140 | 141 | public static void RemoveEndPoint(EndPointListener epl, IPEndPoint ep) 142 | { 143 | lock (ip_to_endpoints) 144 | { 145 | // Dictionary p 146 | Dictionary p; 147 | if (ip_to_endpoints.TryGetValue(ep.Address, out p)) 148 | { 149 | p.Remove(ep.Port); 150 | if (p.Count == 0) 151 | { 152 | ip_to_endpoints.Remove(ep.Address); 153 | } 154 | } 155 | epl.Close(); 156 | } 157 | } 158 | 159 | public static void RemoveListener(ILogger logger, HttpListener listener) 160 | { 161 | lock (ip_to_endpoints) 162 | { 163 | foreach (string prefix in listener.Prefixes) 164 | { 165 | RemovePrefixInternal(logger, prefix, listener); 166 | } 167 | } 168 | } 169 | 170 | public static void RemovePrefix(ILogger logger, string prefix, HttpListener listener) 171 | { 172 | lock (ip_to_endpoints) 173 | { 174 | RemovePrefixInternal(logger, prefix, listener); 175 | } 176 | } 177 | 178 | static void RemovePrefixInternal(ILogger logger, string prefix, HttpListener listener) 179 | { 180 | ListenerPrefix lp = new ListenerPrefix(prefix); 181 | if (lp.Path.IndexOf('%') != -1) 182 | return; 183 | 184 | if (lp.Path.IndexOf("//", StringComparison.Ordinal) != -1) 185 | return; 186 | 187 | EndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure); 188 | epl.RemovePrefix(lp, listener); 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /SocketHttpListener/Net/HttpListener.cs: -------------------------------------------------------------------------------- 1 | // TODO: Logging. 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using Patterns.Logging; 6 | using System.Security.Cryptography.X509Certificates; 7 | using System.IO; 8 | using System.Net; 9 | 10 | namespace SocketHttpListener.Net 11 | { 12 | public sealed class HttpListener : IDisposable 13 | { 14 | AuthenticationSchemes auth_schemes; 15 | HttpListenerPrefixCollection prefixes; 16 | AuthenticationSchemeSelector auth_selector; 17 | string realm; 18 | bool ignore_write_exceptions; 19 | bool unsafe_ntlm_auth; 20 | bool listening; 21 | bool disposed; 22 | 23 | Dictionary registry; // Dictionary 24 | Dictionary connections; 25 | private ILogger _logger; 26 | private X509Certificate2 _certificate; 27 | 28 | public Action OnContext { get; set; } 29 | 30 | public HttpListener() 31 | : this(new NullLogger()) 32 | { 33 | } 34 | 35 | public HttpListener(ILogger logger) 36 | { 37 | _logger = logger; 38 | prefixes = new HttpListenerPrefixCollection(logger, this); 39 | registry = new Dictionary(); 40 | connections = new Dictionary(); 41 | auth_schemes = AuthenticationSchemes.Anonymous; 42 | } 43 | 44 | public HttpListener(X509Certificate2 certificate) 45 | :this(new NullLogger(), certificate) 46 | { 47 | } 48 | 49 | public HttpListener(string certificateLocation) 50 | : this(new NullLogger(), certificateLocation) 51 | { 52 | } 53 | 54 | public HttpListener(ILogger logger, X509Certificate2 certificate) 55 | : this(logger) 56 | { 57 | _certificate = certificate; 58 | } 59 | 60 | public HttpListener(ILogger logger, string certificateLocation) 61 | :this(logger) 62 | { 63 | LoadCertificateAndKey(certificateLocation); 64 | } 65 | 66 | // TODO: Digest, NTLM and Negotiate require ControlPrincipal 67 | public AuthenticationSchemes AuthenticationSchemes 68 | { 69 | get { return auth_schemes; } 70 | set 71 | { 72 | CheckDisposed(); 73 | auth_schemes = value; 74 | } 75 | } 76 | 77 | public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate 78 | { 79 | get { return auth_selector; } 80 | set 81 | { 82 | CheckDisposed(); 83 | auth_selector = value; 84 | } 85 | } 86 | 87 | public bool IgnoreWriteExceptions 88 | { 89 | get { return ignore_write_exceptions; } 90 | set 91 | { 92 | CheckDisposed(); 93 | ignore_write_exceptions = value; 94 | } 95 | } 96 | 97 | public bool IsListening 98 | { 99 | get { return listening; } 100 | } 101 | 102 | public static bool IsSupported 103 | { 104 | get { return true; } 105 | } 106 | 107 | public HttpListenerPrefixCollection Prefixes 108 | { 109 | get 110 | { 111 | CheckDisposed(); 112 | return prefixes; 113 | } 114 | } 115 | 116 | // TODO: use this 117 | public string Realm 118 | { 119 | get { return realm; } 120 | set 121 | { 122 | CheckDisposed(); 123 | realm = value; 124 | } 125 | } 126 | 127 | public bool UnsafeConnectionNtlmAuthentication 128 | { 129 | get { return unsafe_ntlm_auth; } 130 | set 131 | { 132 | CheckDisposed(); 133 | unsafe_ntlm_auth = value; 134 | } 135 | } 136 | 137 | void LoadCertificateAndKey(string certificateLocation) 138 | { 139 | // Actually load the certificate 140 | try 141 | { 142 | _logger.Info("attempting to load pfx: {0}", certificateLocation); 143 | if (!File.Exists(certificateLocation)) 144 | { 145 | _logger.Error("Secure requested, but no certificate found at: {0}", certificateLocation); 146 | return; 147 | } 148 | 149 | X509Certificate2 localCert = new X509Certificate2(certificateLocation); 150 | //localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA; 151 | if (localCert.PrivateKey == null) 152 | { 153 | _logger.Error("Secure requested, no private key included in: {0}", certificateLocation); 154 | return; 155 | } 156 | 157 | _certificate = localCert; 158 | } 159 | catch (Exception e) 160 | { 161 | _logger.ErrorException("Exception loading certificate: {0}", e, certificateLocation ?? ""); 162 | // ignore errors 163 | } 164 | } 165 | 166 | //internal IMonoSslStream CreateSslStream(Stream innerStream, bool ownsStream, MSI.MonoRemoteCertificateValidationCallback callback) 167 | //{ 168 | // lock (registry) 169 | // { 170 | // if (tlsProvider == null) 171 | // tlsProvider = MonoTlsProviderFactory.GetProviderInternal(); 172 | // if (tlsSettings == null) 173 | // tlsSettings = MSI.MonoTlsSettings.CopyDefaultSettings(); 174 | // if (tlsSettings.RemoteCertificateValidationCallback == null) 175 | // tlsSettings.RemoteCertificateValidationCallback = callback; 176 | // return tlsProvider.CreateSslStream(innerStream, ownsStream, tlsSettings); 177 | // } 178 | //} 179 | 180 | internal X509Certificate2 Certificate 181 | { 182 | get { return _certificate; } 183 | } 184 | 185 | public void Abort() 186 | { 187 | if (disposed) 188 | return; 189 | 190 | if (!listening) 191 | { 192 | return; 193 | } 194 | 195 | Close(true); 196 | } 197 | 198 | public void Close() 199 | { 200 | if (disposed) 201 | return; 202 | 203 | if (!listening) 204 | { 205 | disposed = true; 206 | return; 207 | } 208 | 209 | Close(true); 210 | disposed = true; 211 | } 212 | 213 | void Close(bool force) 214 | { 215 | CheckDisposed(); 216 | EndPointManager.RemoveListener(_logger, this); 217 | Cleanup(force); 218 | } 219 | 220 | void Cleanup(bool close_existing) 221 | { 222 | lock (registry) 223 | { 224 | if (close_existing) 225 | { 226 | // Need to copy this since closing will call UnregisterContext 227 | ICollection keys = registry.Keys; 228 | var all = new HttpListenerContext[keys.Count]; 229 | keys.CopyTo(all, 0); 230 | registry.Clear(); 231 | for (int i = all.Length - 1; i >= 0; i--) 232 | all[i].Connection.Close(true); 233 | } 234 | 235 | lock (connections) 236 | { 237 | ICollection keys = connections.Keys; 238 | var conns = new HttpConnection[keys.Count]; 239 | keys.CopyTo(conns, 0); 240 | connections.Clear(); 241 | for (int i = conns.Length - 1; i >= 0; i--) 242 | conns[i].Close(true); 243 | } 244 | } 245 | } 246 | 247 | internal AuthenticationSchemes SelectAuthenticationScheme(HttpListenerContext context) 248 | { 249 | if (AuthenticationSchemeSelectorDelegate != null) 250 | return AuthenticationSchemeSelectorDelegate(context.Request); 251 | else 252 | return auth_schemes; 253 | } 254 | 255 | public void Start() 256 | { 257 | CheckDisposed(); 258 | if (listening) 259 | return; 260 | 261 | EndPointManager.AddListener(_logger, this); 262 | listening = true; 263 | } 264 | 265 | public void Stop() 266 | { 267 | CheckDisposed(); 268 | listening = false; 269 | Close(false); 270 | } 271 | 272 | void IDisposable.Dispose() 273 | { 274 | if (disposed) 275 | return; 276 | 277 | Close(true); //TODO: Should we force here or not? 278 | disposed = true; 279 | } 280 | 281 | internal void CheckDisposed() 282 | { 283 | if (disposed) 284 | throw new ObjectDisposedException(GetType().ToString()); 285 | } 286 | 287 | internal void RegisterContext(HttpListenerContext context) 288 | { 289 | if (OnContext != null && IsListening) 290 | { 291 | OnContext(context); 292 | } 293 | 294 | lock (registry) 295 | registry[context] = context; 296 | } 297 | 298 | internal void UnregisterContext(HttpListenerContext context) 299 | { 300 | lock (registry) 301 | registry.Remove(context); 302 | } 303 | 304 | internal void AddConnection(HttpConnection cnc) 305 | { 306 | lock (connections) 307 | { 308 | connections[cnc] = cnc; 309 | } 310 | } 311 | 312 | internal void RemoveConnection(HttpConnection cnc) 313 | { 314 | lock (connections) 315 | { 316 | connections.Remove(cnc); 317 | } 318 | } 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /SocketHttpListener/Net/HttpListenerBasicIdentity.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Principal; 2 | 3 | namespace SocketHttpListener.Net 4 | { 5 | public class HttpListenerBasicIdentity : GenericIdentity 6 | { 7 | string password; 8 | 9 | public HttpListenerBasicIdentity(string username, string password) 10 | : base(username, "Basic") 11 | { 12 | this.password = password; 13 | } 14 | 15 | public virtual string Password 16 | { 17 | get { return password; } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SocketHttpListener/Net/HttpListenerContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Security.Principal; 4 | using Patterns.Logging; 5 | using SocketHttpListener.Net.WebSockets; 6 | 7 | namespace SocketHttpListener.Net 8 | { 9 | public sealed class HttpListenerContext 10 | { 11 | HttpListenerRequest request; 12 | HttpListenerResponse response; 13 | IPrincipal user; 14 | HttpConnection cnc; 15 | string error; 16 | int err_status = 400; 17 | internal HttpListener Listener; 18 | private readonly ILogger _logger; 19 | 20 | internal HttpListenerContext(HttpConnection cnc, ILogger logger) 21 | { 22 | this.cnc = cnc; 23 | _logger = logger; 24 | request = new HttpListenerRequest(this); 25 | response = new HttpListenerResponse(this, _logger); 26 | } 27 | 28 | internal int ErrorStatus 29 | { 30 | get { return err_status; } 31 | set { err_status = value; } 32 | } 33 | 34 | internal string ErrorMessage 35 | { 36 | get { return error; } 37 | set { error = value; } 38 | } 39 | 40 | internal bool HaveError 41 | { 42 | get { return (error != null); } 43 | } 44 | 45 | internal HttpConnection Connection 46 | { 47 | get { return cnc; } 48 | } 49 | 50 | public HttpListenerRequest Request 51 | { 52 | get { return request; } 53 | } 54 | 55 | public HttpListenerResponse Response 56 | { 57 | get { return response; } 58 | } 59 | 60 | public IPrincipal User 61 | { 62 | get { return user; } 63 | } 64 | 65 | internal void ParseAuthentication(AuthenticationSchemes expectedSchemes) 66 | { 67 | if (expectedSchemes == AuthenticationSchemes.Anonymous) 68 | return; 69 | 70 | // TODO: Handle NTLM/Digest modes 71 | string header = request.Headers["Authorization"]; 72 | if (header == null || header.Length < 2) 73 | return; 74 | 75 | string[] authenticationData = header.Split(new char[] { ' ' }, 2); 76 | if (string.Compare(authenticationData[0], "basic", true) == 0) 77 | { 78 | user = ParseBasicAuthentication(authenticationData[1]); 79 | } 80 | // TODO: throw if malformed -> 400 bad request 81 | } 82 | 83 | internal IPrincipal ParseBasicAuthentication(string authData) 84 | { 85 | try 86 | { 87 | // Basic AUTH Data is a formatted Base64 String 88 | //string domain = null; 89 | string user = null; 90 | string password = null; 91 | int pos = -1; 92 | string authString = System.Text.Encoding.Default.GetString(Convert.FromBase64String(authData)); 93 | 94 | // The format is DOMAIN\username:password 95 | // Domain is optional 96 | 97 | pos = authString.IndexOf(':'); 98 | 99 | // parse the password off the end 100 | password = authString.Substring(pos + 1); 101 | 102 | // discard the password 103 | authString = authString.Substring(0, pos); 104 | 105 | // check if there is a domain 106 | pos = authString.IndexOf('\\'); 107 | 108 | if (pos > 0) 109 | { 110 | //domain = authString.Substring (0, pos); 111 | user = authString.Substring(pos); 112 | } 113 | else 114 | { 115 | user = authString; 116 | } 117 | 118 | HttpListenerBasicIdentity identity = new HttpListenerBasicIdentity(user, password); 119 | // TODO: What are the roles MS sets 120 | return new GenericPrincipal(identity, new string[0]); 121 | } 122 | catch (Exception) 123 | { 124 | // Invalid auth data is swallowed silently 125 | return null; 126 | } 127 | } 128 | 129 | public HttpListenerWebSocketContext AcceptWebSocket(string protocol) 130 | { 131 | if (protocol != null) 132 | { 133 | if (protocol.Length == 0) 134 | throw new ArgumentException("An empty string.", "protocol"); 135 | 136 | if (!protocol.IsToken()) 137 | throw new ArgumentException("Contains an invalid character.", "protocol"); 138 | } 139 | 140 | return new HttpListenerWebSocketContext(this, protocol); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /SocketHttpListener/Net/HttpListenerPrefixCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using Patterns.Logging; 5 | 6 | namespace SocketHttpListener.Net 7 | { 8 | public class HttpListenerPrefixCollection : ICollection, IEnumerable, IEnumerable 9 | { 10 | List prefixes = new List(); 11 | HttpListener listener; 12 | 13 | private ILogger _logger; 14 | 15 | internal HttpListenerPrefixCollection(ILogger logger, HttpListener listener) 16 | { 17 | _logger = logger; 18 | this.listener = listener; 19 | } 20 | 21 | public int Count 22 | { 23 | get { return prefixes.Count; } 24 | } 25 | 26 | public bool IsReadOnly 27 | { 28 | get { return false; } 29 | } 30 | 31 | public bool IsSynchronized 32 | { 33 | get { return false; } 34 | } 35 | 36 | public void Add(string uriPrefix) 37 | { 38 | listener.CheckDisposed(); 39 | ListenerPrefix.CheckUri(uriPrefix); 40 | if (prefixes.Contains(uriPrefix)) 41 | return; 42 | 43 | prefixes.Add(uriPrefix); 44 | if (listener.IsListening) 45 | EndPointManager.AddPrefix(_logger, uriPrefix, listener); 46 | } 47 | 48 | public void Clear() 49 | { 50 | listener.CheckDisposed(); 51 | prefixes.Clear(); 52 | if (listener.IsListening) 53 | EndPointManager.RemoveListener(_logger, listener); 54 | } 55 | 56 | public bool Contains(string uriPrefix) 57 | { 58 | listener.CheckDisposed(); 59 | return prefixes.Contains(uriPrefix); 60 | } 61 | 62 | public void CopyTo(string[] array, int offset) 63 | { 64 | listener.CheckDisposed(); 65 | prefixes.CopyTo(array, offset); 66 | } 67 | 68 | public void CopyTo(Array array, int offset) 69 | { 70 | listener.CheckDisposed(); 71 | ((ICollection)prefixes).CopyTo(array, offset); 72 | } 73 | 74 | public IEnumerator GetEnumerator() 75 | { 76 | return prefixes.GetEnumerator(); 77 | } 78 | 79 | IEnumerator IEnumerable.GetEnumerator() 80 | { 81 | return prefixes.GetEnumerator(); 82 | } 83 | 84 | public bool Remove(string uriPrefix) 85 | { 86 | listener.CheckDisposed(); 87 | if (uriPrefix == null) 88 | throw new ArgumentNullException("uriPrefix"); 89 | 90 | bool result = prefixes.Remove(uriPrefix); 91 | if (result && listener.IsListening) 92 | EndPointManager.RemovePrefix(_logger, uriPrefix, listener); 93 | 94 | return result; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /SocketHttpListener/Net/HttpStatusCode.cs: -------------------------------------------------------------------------------- 1 | namespace SocketHttpListener.Net 2 | { 3 | /// 4 | /// Contains the values of the HTTP status codes. 5 | /// 6 | /// 7 | /// The HttpStatusCode enumeration contains the values of the HTTP status codes defined in 8 | /// RFC 2616 for HTTP 1.1. 9 | /// 10 | public enum HttpStatusCode 11 | { 12 | /// 13 | /// Equivalent to status code 100. 14 | /// Indicates that the client should continue with its request. 15 | /// 16 | Continue = 100, 17 | /// 18 | /// Equivalent to status code 101. 19 | /// Indicates that the server is switching the HTTP version or protocol on the connection. 20 | /// 21 | SwitchingProtocols = 101, 22 | /// 23 | /// Equivalent to status code 200. 24 | /// Indicates that the client's request has succeeded. 25 | /// 26 | OK = 200, 27 | /// 28 | /// Equivalent to status code 201. 29 | /// Indicates that the client's request has been fulfilled and resulted in a new resource being 30 | /// created. 31 | /// 32 | Created = 201, 33 | /// 34 | /// Equivalent to status code 202. 35 | /// Indicates that the client's request has been accepted for processing, but the processing 36 | /// hasn't been completed. 37 | /// 38 | Accepted = 202, 39 | /// 40 | /// Equivalent to status code 203. 41 | /// Indicates that the returned metainformation is from a local or a third-party copy instead of 42 | /// the origin server. 43 | /// 44 | NonAuthoritativeInformation = 203, 45 | /// 46 | /// Equivalent to status code 204. 47 | /// Indicates that the server has fulfilled the client's request but doesn't need to return 48 | /// an entity-body. 49 | /// 50 | NoContent = 204, 51 | /// 52 | /// Equivalent to status code 205. 53 | /// Indicates that the server has fulfilled the client's request, and the user agent should 54 | /// reset the document view which caused the request to be sent. 55 | /// 56 | ResetContent = 205, 57 | /// 58 | /// Equivalent to status code 206. 59 | /// Indicates that the server has fulfilled the partial GET request for the resource. 60 | /// 61 | PartialContent = 206, 62 | /// 63 | /// 64 | /// Equivalent to status code 300. 65 | /// Indicates that the requested resource corresponds to any of multiple representations. 66 | /// 67 | /// 68 | /// MultipleChoices is a synonym for Ambiguous. 69 | /// 70 | /// 71 | MultipleChoices = 300, 72 | /// 73 | /// 74 | /// Equivalent to status code 300. 75 | /// Indicates that the requested resource corresponds to any of multiple representations. 76 | /// 77 | /// 78 | /// Ambiguous is a synonym for MultipleChoices. 79 | /// 80 | /// 81 | Ambiguous = 300, 82 | /// 83 | /// 84 | /// Equivalent to status code 301. 85 | /// Indicates that the requested resource has been assigned a new permanent URI and 86 | /// any future references to this resource should use one of the returned URIs. 87 | /// 88 | /// 89 | /// MovedPermanently is a synonym for Moved. 90 | /// 91 | /// 92 | MovedPermanently = 301, 93 | /// 94 | /// 95 | /// Equivalent to status code 301. 96 | /// Indicates that the requested resource has been assigned a new permanent URI and 97 | /// any future references to this resource should use one of the returned URIs. 98 | /// 99 | /// 100 | /// Moved is a synonym for MovedPermanently. 101 | /// 102 | /// 103 | Moved = 301, 104 | /// 105 | /// 106 | /// Equivalent to status code 302. 107 | /// Indicates that the requested resource is located temporarily under a different URI. 108 | /// 109 | /// 110 | /// Found is a synonym for Redirect. 111 | /// 112 | /// 113 | Found = 302, 114 | /// 115 | /// 116 | /// Equivalent to status code 302. 117 | /// Indicates that the requested resource is located temporarily under a different URI. 118 | /// 119 | /// 120 | /// Redirect is a synonym for Found. 121 | /// 122 | /// 123 | Redirect = 302, 124 | /// 125 | /// 126 | /// Equivalent to status code 303. 127 | /// Indicates that the response to the request can be found under a different URI and 128 | /// should be retrieved using a GET method on that resource. 129 | /// 130 | /// 131 | /// SeeOther is a synonym for RedirectMethod. 132 | /// 133 | /// 134 | SeeOther = 303, 135 | /// 136 | /// 137 | /// Equivalent to status code 303. 138 | /// Indicates that the response to the request can be found under a different URI and 139 | /// should be retrieved using a GET method on that resource. 140 | /// 141 | /// 142 | /// RedirectMethod is a synonym for SeeOther. 143 | /// 144 | /// 145 | RedirectMethod = 303, 146 | /// 147 | /// Equivalent to status code 304. 148 | /// Indicates that the client has performed a conditional GET request and access is allowed, 149 | /// but the document hasn't been modified. 150 | /// 151 | NotModified = 304, 152 | /// 153 | /// Equivalent to status code 305. 154 | /// Indicates that the requested resource must be accessed through the proxy given by 155 | /// the Location field. 156 | /// 157 | UseProxy = 305, 158 | /// 159 | /// Equivalent to status code 306. 160 | /// This status code was used in a previous version of the specification, is no longer used, 161 | /// and is reserved for future use. 162 | /// 163 | Unused = 306, 164 | /// 165 | /// 166 | /// Equivalent to status code 307. 167 | /// Indicates that the requested resource is located temporarily under a different URI. 168 | /// 169 | /// 170 | /// TemporaryRedirect is a synonym for RedirectKeepVerb. 171 | /// 172 | /// 173 | TemporaryRedirect = 307, 174 | /// 175 | /// 176 | /// Equivalent to status code 307. 177 | /// Indicates that the requested resource is located temporarily under a different URI. 178 | /// 179 | /// 180 | /// RedirectKeepVerb is a synonym for TemporaryRedirect. 181 | /// 182 | /// 183 | RedirectKeepVerb = 307, 184 | /// 185 | /// Equivalent to status code 400. 186 | /// Indicates that the client's request couldn't be understood by the server due to 187 | /// malformed syntax. 188 | /// 189 | BadRequest = 400, 190 | /// 191 | /// Equivalent to status code 401. 192 | /// Indicates that the client's request requires user authentication. 193 | /// 194 | Unauthorized = 401, 195 | /// 196 | /// Equivalent to status code 402. 197 | /// This status code is reserved for future use. 198 | /// 199 | PaymentRequired = 402, 200 | /// 201 | /// Equivalent to status code 403. 202 | /// Indicates that the server understood the client's request but is refusing to fulfill it. 203 | /// 204 | Forbidden = 403, 205 | /// 206 | /// Equivalent to status code 404. 207 | /// Indicates that the server hasn't found anything matching the request URI. 208 | /// 209 | NotFound = 404, 210 | /// 211 | /// Equivalent to status code 405. 212 | /// Indicates that the method specified in the request line isn't allowed for the resource 213 | /// identified by the request URI. 214 | /// 215 | MethodNotAllowed = 405, 216 | /// 217 | /// Equivalent to status code 406. 218 | /// Indicates that the server doesn't have the appropriate resource to respond to the Accept 219 | /// headers in the client's request. 220 | /// 221 | NotAcceptable = 406, 222 | /// 223 | /// Equivalent to status code 407. 224 | /// Indicates that the client must first authenticate itself with the proxy. 225 | /// 226 | ProxyAuthenticationRequired = 407, 227 | /// 228 | /// Equivalent to status code 408. 229 | /// Indicates that the client didn't produce a request within the time that the server was 230 | /// prepared to wait. 231 | /// 232 | RequestTimeout = 408, 233 | /// 234 | /// Equivalent to status code 409. 235 | /// Indicates that the client's request couldn't be completed due to a conflict on the server. 236 | /// 237 | Conflict = 409, 238 | /// 239 | /// Equivalent to status code 410. 240 | /// Indicates that the requested resource is no longer available at the server and 241 | /// no forwarding address is known. 242 | /// 243 | Gone = 410, 244 | /// 245 | /// Equivalent to status code 411. 246 | /// Indicates that the server refuses to accept the client's request without a defined 247 | /// Content-Length. 248 | /// 249 | LengthRequired = 411, 250 | /// 251 | /// Equivalent to status code 412. 252 | /// Indicates that the precondition given in one or more of the request headers evaluated to 253 | /// false when it was tested on the server. 254 | /// 255 | PreconditionFailed = 412, 256 | /// 257 | /// Equivalent to status code 413. 258 | /// Indicates that the entity of the client's request is larger than the server is willing or 259 | /// able to process. 260 | /// 261 | RequestEntityTooLarge = 413, 262 | /// 263 | /// Equivalent to status code 414. 264 | /// Indicates that the request URI is longer than the server is willing to interpret. 265 | /// 266 | RequestUriTooLong = 414, 267 | /// 268 | /// Equivalent to status code 415. 269 | /// Indicates that the entity of the client's request is in a format not supported by 270 | /// the requested resource for the requested method. 271 | /// 272 | UnsupportedMediaType = 415, 273 | /// 274 | /// Equivalent to status code 416. 275 | /// Indicates that none of the range specifier values in a Range request header overlap 276 | /// the current extent of the selected resource. 277 | /// 278 | RequestedRangeNotSatisfiable = 416, 279 | /// 280 | /// Equivalent to status code 417. 281 | /// Indicates that the expectation given in an Expect request header couldn't be met by 282 | /// the server. 283 | /// 284 | ExpectationFailed = 417, 285 | /// 286 | /// Equivalent to status code 500. 287 | /// Indicates that the server encountered an unexpected condition which prevented it from 288 | /// fulfilling the client's request. 289 | /// 290 | InternalServerError = 500, 291 | /// 292 | /// Equivalent to status code 501. 293 | /// Indicates that the server doesn't support the functionality required to fulfill the client's 294 | /// request. 295 | /// 296 | NotImplemented = 501, 297 | /// 298 | /// Equivalent to status code 502. 299 | /// Indicates that a gateway or proxy server received an invalid response from the upstream 300 | /// server. 301 | /// 302 | BadGateway = 502, 303 | /// 304 | /// Equivalent to status code 503. 305 | /// Indicates that the server is currently unable to handle the client's request due to 306 | /// a temporary overloading or maintenance of the server. 307 | /// 308 | ServiceUnavailable = 503, 309 | /// 310 | /// Equivalent to status code 504. 311 | /// Indicates that a gateway or proxy server didn't receive a timely response from the upstream 312 | /// server or some other auxiliary server. 313 | /// 314 | GatewayTimeout = 504, 315 | /// 316 | /// Equivalent to status code 505. 317 | /// Indicates that the server doesn't support the HTTP version used in the client's request. 318 | /// 319 | HttpVersionNotSupported = 505, 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /SocketHttpListener/Net/HttpStreamAsyncResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace SocketHttpListener.Net 5 | { 6 | class HttpStreamAsyncResult : IAsyncResult 7 | { 8 | object locker = new object(); 9 | ManualResetEvent handle; 10 | bool completed; 11 | 12 | internal byte[] Buffer; 13 | internal int Offset; 14 | internal int Count; 15 | internal AsyncCallback Callback; 16 | internal object State; 17 | internal int SynchRead; 18 | internal Exception Error; 19 | 20 | public void Complete(Exception e) 21 | { 22 | Error = e; 23 | Complete(); 24 | } 25 | 26 | public void Complete() 27 | { 28 | lock (locker) 29 | { 30 | if (completed) 31 | return; 32 | 33 | completed = true; 34 | if (handle != null) 35 | handle.Set(); 36 | 37 | if (Callback != null) 38 | Callback.BeginInvoke(this, null, null); 39 | } 40 | } 41 | 42 | public object AsyncState 43 | { 44 | get { return State; } 45 | } 46 | 47 | public WaitHandle AsyncWaitHandle 48 | { 49 | get 50 | { 51 | lock (locker) 52 | { 53 | if (handle == null) 54 | handle = new ManualResetEvent(completed); 55 | } 56 | 57 | return handle; 58 | } 59 | } 60 | 61 | public bool CompletedSynchronously 62 | { 63 | get { return (SynchRead == Count); } 64 | } 65 | 66 | public bool IsCompleted 67 | { 68 | get 69 | { 70 | lock (locker) 71 | { 72 | return completed; 73 | } 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /SocketHttpListener/Net/HttpVersion.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SocketHttpListener.Net 4 | { 5 | // 6 | // 7 | public class HttpVersion 8 | { 9 | 10 | public static readonly Version Version10 = new Version(1, 0); 11 | public static readonly Version Version11 = new Version(1, 1); 12 | 13 | // pretty useless.. 14 | public HttpVersion() { } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SocketHttpListener/Net/ListenerPrefix.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace SocketHttpListener.Net 5 | { 6 | sealed class ListenerPrefix 7 | { 8 | string original; 9 | string host; 10 | ushort port; 11 | string path; 12 | bool secure; 13 | IPAddress[] addresses; 14 | public HttpListener Listener; 15 | 16 | public ListenerPrefix(string prefix) 17 | { 18 | this.original = prefix; 19 | Parse(prefix); 20 | } 21 | 22 | public override string ToString() 23 | { 24 | return original; 25 | } 26 | 27 | public IPAddress[] Addresses 28 | { 29 | get { return addresses; } 30 | set { addresses = value; } 31 | } 32 | public bool Secure 33 | { 34 | get { return secure; } 35 | } 36 | 37 | public string Host 38 | { 39 | get { return host; } 40 | } 41 | 42 | public int Port 43 | { 44 | get { return (int)port; } 45 | } 46 | 47 | public string Path 48 | { 49 | get { return path; } 50 | } 51 | 52 | // Equals and GetHashCode are required to detect duplicates in HttpListenerPrefixCollection. 53 | public override bool Equals(object o) 54 | { 55 | ListenerPrefix other = o as ListenerPrefix; 56 | if (other == null) 57 | return false; 58 | 59 | return (original == other.original); 60 | } 61 | 62 | public override int GetHashCode() 63 | { 64 | return original.GetHashCode(); 65 | } 66 | 67 | void Parse(string uri) 68 | { 69 | ushort default_port = 80; 70 | if (uri.StartsWith("https://")) 71 | { 72 | default_port = 443; 73 | secure = true; 74 | } 75 | 76 | int length = uri.Length; 77 | int start_host = uri.IndexOf(':') + 3; 78 | if (start_host >= length) 79 | throw new ArgumentException("No host specified."); 80 | 81 | int colon = uri.IndexOf(':', start_host, length - start_host); 82 | int root; 83 | if (colon > 0) 84 | { 85 | host = uri.Substring(start_host, colon - start_host); 86 | root = uri.IndexOf('/', colon, length - colon); 87 | port = (ushort)Int32.Parse(uri.Substring(colon + 1, root - colon - 1)); 88 | path = uri.Substring(root); 89 | } 90 | else 91 | { 92 | root = uri.IndexOf('/', start_host, length - start_host); 93 | host = uri.Substring(start_host, root - start_host); 94 | port = default_port; 95 | path = uri.Substring(root); 96 | } 97 | if (path.Length != 1) 98 | path = path.Substring(0, path.Length - 1); 99 | } 100 | 101 | public static void CheckUri(string uri) 102 | { 103 | if (uri == null) 104 | throw new ArgumentNullException("uriPrefix"); 105 | 106 | if (!uri.StartsWith("http://") && !uri.StartsWith("https://")) 107 | throw new ArgumentException("Only 'http' and 'https' schemes are supported."); 108 | 109 | int length = uri.Length; 110 | int start_host = uri.IndexOf(':') + 3; 111 | if (start_host >= length) 112 | throw new ArgumentException("No host specified."); 113 | 114 | int colon = uri.IndexOf(':', start_host, length - start_host); 115 | if (start_host == colon) 116 | throw new ArgumentException("No host specified."); 117 | 118 | int root; 119 | if (colon > 0) 120 | { 121 | root = uri.IndexOf('/', colon, length - colon); 122 | if (root == -1) 123 | throw new ArgumentException("No path specified."); 124 | 125 | try 126 | { 127 | int p = Int32.Parse(uri.Substring(colon + 1, root - colon - 1)); 128 | if (p <= 0 || p >= 65536) 129 | throw new Exception(); 130 | } 131 | catch 132 | { 133 | throw new ArgumentException("Invalid port."); 134 | } 135 | } 136 | else 137 | { 138 | root = uri.IndexOf('/', start_host, length - start_host); 139 | if (root == -1) 140 | throw new ArgumentException("No path specified."); 141 | } 142 | 143 | if (uri[uri.Length - 1] != '/') 144 | throw new ArgumentException("The prefix must end with '/'"); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /SocketHttpListener/Net/RequestStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace SocketHttpListener.Net 6 | { 7 | class RequestStream : Stream 8 | { 9 | byte[] buffer; 10 | int offset; 11 | int length; 12 | long remaining_body; 13 | bool disposed; 14 | Stream stream; 15 | 16 | internal RequestStream(Stream stream, byte[] buffer, int offset, int length) 17 | : this(stream, buffer, offset, length, -1) 18 | { 19 | } 20 | 21 | internal RequestStream(Stream stream, byte[] buffer, int offset, int length, long contentlength) 22 | { 23 | this.stream = stream; 24 | this.buffer = buffer; 25 | this.offset = offset; 26 | this.length = length; 27 | this.remaining_body = contentlength; 28 | } 29 | 30 | public override bool CanRead 31 | { 32 | get { return true; } 33 | } 34 | 35 | public override bool CanSeek 36 | { 37 | get { return false; } 38 | } 39 | 40 | public override bool CanWrite 41 | { 42 | get { return false; } 43 | } 44 | 45 | public override long Length 46 | { 47 | get { throw new NotSupportedException(); } 48 | } 49 | 50 | public override long Position 51 | { 52 | get { throw new NotSupportedException(); } 53 | set { throw new NotSupportedException(); } 54 | } 55 | 56 | 57 | public override void Close() 58 | { 59 | disposed = true; 60 | } 61 | 62 | public override void Flush() 63 | { 64 | } 65 | 66 | 67 | // Returns 0 if we can keep reading from the base stream, 68 | // > 0 if we read something from the buffer. 69 | // -1 if we had a content length set and we finished reading that many bytes. 70 | int FillFromBuffer(byte[] buffer, int off, int count) 71 | { 72 | if (buffer == null) 73 | throw new ArgumentNullException("buffer"); 74 | if (off < 0) 75 | throw new ArgumentOutOfRangeException("offset", "< 0"); 76 | if (count < 0) 77 | throw new ArgumentOutOfRangeException("count", "< 0"); 78 | int len = buffer.Length; 79 | if (off > len) 80 | throw new ArgumentException("destination offset is beyond array size"); 81 | if (off > len - count) 82 | throw new ArgumentException("Reading would overrun buffer"); 83 | 84 | if (this.remaining_body == 0) 85 | return -1; 86 | 87 | if (this.length == 0) 88 | return 0; 89 | 90 | int size = Math.Min(this.length, count); 91 | if (this.remaining_body > 0) 92 | size = (int)Math.Min(size, this.remaining_body); 93 | 94 | if (this.offset > this.buffer.Length - size) 95 | { 96 | size = Math.Min(size, this.buffer.Length - this.offset); 97 | } 98 | if (size == 0) 99 | return 0; 100 | 101 | Buffer.BlockCopy(this.buffer, this.offset, buffer, off, size); 102 | this.offset += size; 103 | this.length -= size; 104 | if (this.remaining_body > 0) 105 | remaining_body -= size; 106 | return size; 107 | } 108 | 109 | public override int Read([In, Out] byte[] buffer, int offset, int count) 110 | { 111 | if (disposed) 112 | throw new ObjectDisposedException(typeof(RequestStream).ToString()); 113 | 114 | // Call FillFromBuffer to check for buffer boundaries even when remaining_body is 0 115 | int nread = FillFromBuffer(buffer, offset, count); 116 | if (nread == -1) 117 | { // No more bytes available (Content-Length) 118 | return 0; 119 | } 120 | else if (nread > 0) 121 | { 122 | return nread; 123 | } 124 | 125 | nread = stream.Read(buffer, offset, count); 126 | if (nread > 0 && remaining_body > 0) 127 | remaining_body -= nread; 128 | return nread; 129 | } 130 | 131 | public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, 132 | AsyncCallback cback, object state) 133 | { 134 | if (disposed) 135 | throw new ObjectDisposedException(typeof(RequestStream).ToString()); 136 | 137 | int nread = FillFromBuffer(buffer, offset, count); 138 | if (nread > 0 || nread == -1) 139 | { 140 | HttpStreamAsyncResult ares = new HttpStreamAsyncResult(); 141 | ares.Buffer = buffer; 142 | ares.Offset = offset; 143 | ares.Count = count; 144 | ares.Callback = cback; 145 | ares.State = state; 146 | ares.SynchRead = Math.Max(0, nread); 147 | ares.Complete(); 148 | return ares; 149 | } 150 | 151 | // Avoid reading past the end of the request to allow 152 | // for HTTP pipelining 153 | if (remaining_body >= 0 && count > remaining_body) 154 | count = (int)Math.Min(Int32.MaxValue, remaining_body); 155 | return stream.BeginRead(buffer, offset, count, cback, state); 156 | } 157 | 158 | public override int EndRead(IAsyncResult ares) 159 | { 160 | if (disposed) 161 | throw new ObjectDisposedException(typeof(RequestStream).ToString()); 162 | 163 | if (ares == null) 164 | throw new ArgumentNullException("async_result"); 165 | 166 | if (ares is HttpStreamAsyncResult) 167 | { 168 | HttpStreamAsyncResult r = (HttpStreamAsyncResult)ares; 169 | if (!ares.IsCompleted) 170 | ares.AsyncWaitHandle.WaitOne(); 171 | return r.SynchRead; 172 | } 173 | 174 | // Close on exception? 175 | int nread = stream.EndRead(ares); 176 | if (remaining_body > 0 && nread > 0) 177 | remaining_body -= nread; 178 | return nread; 179 | } 180 | 181 | public override long Seek(long offset, SeekOrigin origin) 182 | { 183 | throw new NotSupportedException(); 184 | } 185 | 186 | public override void SetLength(long value) 187 | { 188 | throw new NotSupportedException(); 189 | } 190 | 191 | public override void Write(byte[] buffer, int offset, int count) 192 | { 193 | throw new NotSupportedException(); 194 | } 195 | 196 | public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, 197 | AsyncCallback cback, object state) 198 | { 199 | throw new NotSupportedException(); 200 | } 201 | 202 | public override void EndWrite(IAsyncResult async_result) 203 | { 204 | throw new NotSupportedException(); 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /SocketHttpListener/Net/ResponseStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Patterns.Logging; 8 | 9 | namespace SocketHttpListener.Net 10 | { 11 | // FIXME: Does this buffer the response until Close? 12 | // Update: we send a single packet for the first non-chunked Write 13 | // What happens when we set content-length to X and write X-1 bytes then close? 14 | // what if we don't set content-length at all? 15 | class ResponseStream : Stream 16 | { 17 | HttpListenerResponse response; 18 | bool ignore_errors; 19 | bool disposed; 20 | bool trailer_sent; 21 | Stream stream; 22 | 23 | internal ResponseStream(Stream stream, HttpListenerResponse response, bool ignore_errors) 24 | { 25 | this.response = response; 26 | this.ignore_errors = ignore_errors; 27 | this.stream = stream; 28 | } 29 | 30 | public override bool CanRead 31 | { 32 | get { return false; } 33 | } 34 | 35 | public override bool CanSeek 36 | { 37 | get { return false; } 38 | } 39 | 40 | public override bool CanWrite 41 | { 42 | get { return true; } 43 | } 44 | 45 | public override long Length 46 | { 47 | get { throw new NotSupportedException(); } 48 | } 49 | 50 | public override long Position 51 | { 52 | get { throw new NotSupportedException(); } 53 | set { throw new NotSupportedException(); } 54 | } 55 | 56 | 57 | protected override void Dispose(bool disposing) 58 | { 59 | if (disposed == false) 60 | { 61 | disposed = true; 62 | byte[] bytes = null; 63 | MemoryStream ms = GetHeaders(true); 64 | bool chunked = response.SendChunked; 65 | if (stream.CanWrite) 66 | { 67 | try 68 | { 69 | if (ms != null) 70 | { 71 | long start = ms.Position; 72 | if (chunked && !trailer_sent) 73 | { 74 | bytes = GetChunkSizeBytes(0, true); 75 | ms.Position = ms.Length; 76 | ms.Write(bytes, 0, bytes.Length); 77 | } 78 | InternalWrite(ms.ToArray(), (int)start, (int)(ms.Length - start)); 79 | trailer_sent = true; 80 | } 81 | else if (chunked && !trailer_sent) 82 | { 83 | bytes = GetChunkSizeBytes(0, true); 84 | InternalWrite(bytes, 0, bytes.Length); 85 | trailer_sent = true; 86 | } 87 | } 88 | catch (IOException ex) 89 | { 90 | // Ignore error due to connection reset by peer 91 | } 92 | } 93 | response.Close(); 94 | } 95 | 96 | base.Dispose(disposing); 97 | } 98 | 99 | MemoryStream GetHeaders(bool closing) 100 | { 101 | // SendHeaders works on shared headers 102 | lock (response.headers_lock) 103 | { 104 | if (response.HeadersSent) 105 | return null; 106 | MemoryStream ms = new MemoryStream(); 107 | response.SendHeaders(closing, ms); 108 | return ms; 109 | } 110 | } 111 | 112 | public override void Flush() 113 | { 114 | } 115 | 116 | static byte[] crlf = new byte[] { 13, 10 }; 117 | static byte[] GetChunkSizeBytes(int size, bool final) 118 | { 119 | string str = String.Format("{0:x}\r\n{1}", size, final ? "\r\n" : ""); 120 | return Encoding.ASCII.GetBytes(str); 121 | } 122 | 123 | internal void InternalWrite(byte[] buffer, int offset, int count) 124 | { 125 | if (ignore_errors) 126 | { 127 | try 128 | { 129 | stream.Write(buffer, offset, count); 130 | } 131 | catch { } 132 | } 133 | else { 134 | stream.Write(buffer, offset, count); 135 | } 136 | } 137 | 138 | public override void Write(byte[] buffer, int offset, int count) 139 | { 140 | if (disposed) 141 | throw new ObjectDisposedException(GetType().ToString()); 142 | 143 | byte[] bytes = null; 144 | MemoryStream ms = GetHeaders(false); 145 | bool chunked = response.SendChunked; 146 | if (ms != null) 147 | { 148 | long start = ms.Position; // After the possible preamble for the encoding 149 | ms.Position = ms.Length; 150 | if (chunked) 151 | { 152 | bytes = GetChunkSizeBytes(count, false); 153 | ms.Write(bytes, 0, bytes.Length); 154 | } 155 | 156 | int new_count = Math.Min(count, 16384 - (int)ms.Position + (int)start); 157 | ms.Write(buffer, offset, new_count); 158 | count -= new_count; 159 | offset += new_count; 160 | InternalWrite(ms.ToArray(), (int)start, (int)(ms.Length - start)); 161 | ms.SetLength(0); 162 | ms.Capacity = 0; // 'dispose' the buffer in ms. 163 | } 164 | else if (chunked) 165 | { 166 | bytes = GetChunkSizeBytes(count, false); 167 | InternalWrite(bytes, 0, bytes.Length); 168 | } 169 | 170 | if (count > 0) 171 | InternalWrite(buffer, offset, count); 172 | if (chunked) 173 | InternalWrite(crlf, 0, 2); 174 | } 175 | 176 | public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 177 | { 178 | if (disposed) 179 | throw new ObjectDisposedException(GetType().ToString()); 180 | 181 | byte[] bytes = null; 182 | MemoryStream ms = GetHeaders(false); 183 | bool chunked = response.SendChunked; 184 | if (ms != null) 185 | { 186 | long start = ms.Position; 187 | ms.Position = ms.Length; 188 | if (chunked) 189 | { 190 | bytes = GetChunkSizeBytes(count, false); 191 | ms.Write(bytes, 0, bytes.Length); 192 | } 193 | ms.Write(buffer, offset, count); 194 | buffer = ms.ToArray(); 195 | offset = (int)start; 196 | count = (int)(ms.Position - start); 197 | } 198 | else if (chunked) 199 | { 200 | bytes = GetChunkSizeBytes(count, false); 201 | InternalWrite(bytes, 0, bytes.Length); 202 | } 203 | 204 | try 205 | { 206 | if (count > 0) 207 | { 208 | await stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); 209 | } 210 | 211 | if (response.SendChunked) 212 | stream.Write(crlf, 0, 2); 213 | } 214 | catch 215 | { 216 | if (!ignore_errors) 217 | { 218 | throw; 219 | } 220 | } 221 | } 222 | 223 | //public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, 224 | // AsyncCallback cback, object state) 225 | //{ 226 | // if (disposed) 227 | // throw new ObjectDisposedException(GetType().ToString()); 228 | 229 | // byte[] bytes = null; 230 | // MemoryStream ms = GetHeaders(false); 231 | // bool chunked = response.SendChunked; 232 | // if (ms != null) 233 | // { 234 | // long start = ms.Position; 235 | // ms.Position = ms.Length; 236 | // if (chunked) 237 | // { 238 | // bytes = GetChunkSizeBytes(count, false); 239 | // ms.Write(bytes, 0, bytes.Length); 240 | // } 241 | // ms.Write(buffer, offset, count); 242 | // buffer = ms.ToArray(); 243 | // offset = (int)start; 244 | // count = (int)(ms.Position - start); 245 | // } 246 | // else if (chunked) 247 | // { 248 | // bytes = GetChunkSizeBytes(count, false); 249 | // InternalWrite(bytes, 0, bytes.Length); 250 | // } 251 | 252 | // return stream.BeginWrite(buffer, offset, count, cback, state); 253 | //} 254 | 255 | //public override void EndWrite(IAsyncResult ares) 256 | //{ 257 | // if (disposed) 258 | // throw new ObjectDisposedException(GetType().ToString()); 259 | 260 | // if (ignore_errors) 261 | // { 262 | // try 263 | // { 264 | // stream.EndWrite(ares); 265 | // if (response.SendChunked) 266 | // stream.Write(crlf, 0, 2); 267 | // } 268 | // catch { } 269 | // } 270 | // else { 271 | // stream.EndWrite(ares); 272 | // if (response.SendChunked) 273 | // stream.Write(crlf, 0, 2); 274 | // } 275 | //} 276 | 277 | public override int Read([In, Out] byte[] buffer, int offset, int count) 278 | { 279 | throw new NotSupportedException(); 280 | } 281 | 282 | //public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, 283 | // AsyncCallback cback, object state) 284 | //{ 285 | // throw new NotSupportedException(); 286 | //} 287 | 288 | //public override int EndRead(IAsyncResult ares) 289 | //{ 290 | // throw new NotSupportedException(); 291 | //} 292 | 293 | public override long Seek(long offset, SeekOrigin origin) 294 | { 295 | throw new NotSupportedException(); 296 | } 297 | 298 | public override void SetLength(long value) 299 | { 300 | throw new NotSupportedException(); 301 | } 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /SocketHttpListener/Net/WebHeaderCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Collections.Specialized; 5 | using System.Net; 6 | using System.Runtime.InteropServices; 7 | using System.Runtime.Serialization; 8 | using System.Text; 9 | 10 | namespace SocketHttpListener.Net 11 | { 12 | [ComVisible(true)] 13 | public class WebHeaderCollection : NameValueCollection 14 | { 15 | [Flags] 16 | internal enum HeaderInfo 17 | { 18 | Request = 1, 19 | Response = 1 << 1, 20 | MultiValue = 1 << 10 21 | } 22 | 23 | static readonly bool[] allowed_chars = { 24 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, 25 | false, false, false, false, false, false, false, false, false, false, false, false, false, false, 26 | false, false, false, false, false, true, false, true, true, true, true, false, false, false, true, 27 | true, false, true, true, false, true, true, true, true, true, true, true, true, true, true, false, 28 | false, false, false, false, false, false, true, true, true, true, true, true, true, true, true, 29 | true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, 30 | false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, 31 | true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, 32 | false, true, false 33 | }; 34 | 35 | static readonly Dictionary headers; 36 | HeaderInfo? headerRestriction; 37 | HeaderInfo? headerConsistency; 38 | 39 | static WebHeaderCollection() 40 | { 41 | headers = new Dictionary(StringComparer.OrdinalIgnoreCase) { 42 | { "Allow", HeaderInfo.MultiValue }, 43 | { "Accept", HeaderInfo.Request | HeaderInfo.MultiValue }, 44 | { "Accept-Charset", HeaderInfo.MultiValue }, 45 | { "Accept-Encoding", HeaderInfo.MultiValue }, 46 | { "Accept-Language", HeaderInfo.MultiValue }, 47 | { "Accept-Ranges", HeaderInfo.MultiValue }, 48 | { "Age", HeaderInfo.Response }, 49 | { "Authorization", HeaderInfo.MultiValue }, 50 | { "Cache-Control", HeaderInfo.MultiValue }, 51 | { "Cookie", HeaderInfo.MultiValue }, 52 | { "Connection", HeaderInfo.Request | HeaderInfo.MultiValue }, 53 | { "Content-Encoding", HeaderInfo.MultiValue }, 54 | { "Content-Length", HeaderInfo.Request | HeaderInfo.Response }, 55 | { "Content-Type", HeaderInfo.Request }, 56 | { "Content-Language", HeaderInfo.MultiValue }, 57 | { "Date", HeaderInfo.Request }, 58 | { "Expect", HeaderInfo.Request | HeaderInfo.MultiValue}, 59 | { "Host", HeaderInfo.Request }, 60 | { "If-Match", HeaderInfo.MultiValue }, 61 | { "If-Modified-Since", HeaderInfo.Request }, 62 | { "If-None-Match", HeaderInfo.MultiValue }, 63 | { "Keep-Alive", HeaderInfo.Response }, 64 | { "Pragma", HeaderInfo.MultiValue }, 65 | { "Proxy-Authenticate", HeaderInfo.MultiValue }, 66 | { "Proxy-Authorization", HeaderInfo.MultiValue }, 67 | { "Proxy-Connection", HeaderInfo.Request | HeaderInfo.MultiValue }, 68 | { "Range", HeaderInfo.Request | HeaderInfo.MultiValue }, 69 | { "Referer", HeaderInfo.Request }, 70 | { "Set-Cookie", HeaderInfo.MultiValue }, 71 | { "Set-Cookie2", HeaderInfo.MultiValue }, 72 | { "Server", HeaderInfo.Response }, 73 | { "TE", HeaderInfo.MultiValue }, 74 | { "Trailer", HeaderInfo.MultiValue }, 75 | { "Transfer-Encoding", HeaderInfo.Request | HeaderInfo.Response | HeaderInfo.MultiValue }, 76 | { "Translate", HeaderInfo.Request | HeaderInfo.Response }, 77 | { "Upgrade", HeaderInfo.MultiValue }, 78 | { "User-Agent", HeaderInfo.Request }, 79 | { "Vary", HeaderInfo.MultiValue }, 80 | { "Via", HeaderInfo.MultiValue }, 81 | { "Warning", HeaderInfo.MultiValue }, 82 | { "WWW-Authenticate", HeaderInfo.Response | HeaderInfo. MultiValue }, 83 | { "SecWebSocketAccept", HeaderInfo.Response }, 84 | { "SecWebSocketExtensions", HeaderInfo.Request | HeaderInfo.Response | HeaderInfo. MultiValue }, 85 | { "SecWebSocketKey", HeaderInfo.Request }, 86 | { "Sec-WebSocket-Protocol", HeaderInfo.Request | HeaderInfo.Response | HeaderInfo. MultiValue }, 87 | { "SecWebSocketVersion", HeaderInfo.Response | HeaderInfo. MultiValue } 88 | }; 89 | } 90 | 91 | // Methods 92 | 93 | public void Add(string header) 94 | { 95 | if (header == null) 96 | throw new ArgumentNullException("header"); 97 | int pos = header.IndexOf(':'); 98 | if (pos == -1) 99 | throw new ArgumentException("no colon found", "header"); 100 | 101 | this.Add(header.Substring(0, pos), header.Substring(pos + 1)); 102 | } 103 | 104 | public override void Add(string name, string value) 105 | { 106 | if (name == null) 107 | throw new ArgumentNullException("name"); 108 | 109 | CheckRestrictedHeader(name); 110 | this.AddWithoutValidate(name, value); 111 | } 112 | 113 | protected void AddWithoutValidate(string headerName, string headerValue) 114 | { 115 | if (!IsHeaderName(headerName)) 116 | throw new ArgumentException("invalid header name: " + headerName, "headerName"); 117 | if (headerValue == null) 118 | headerValue = String.Empty; 119 | else 120 | headerValue = headerValue.Trim(); 121 | if (!IsHeaderValue(headerValue)) 122 | throw new ArgumentException("invalid header value: " + headerValue, "headerValue"); 123 | 124 | AddValue(headerName, headerValue); 125 | } 126 | 127 | internal void AddValue(string headerName, string headerValue) 128 | { 129 | base.Add(headerName, headerValue); 130 | } 131 | 132 | internal string[] GetValues_internal(string header, bool split) 133 | { 134 | if (header == null) 135 | throw new ArgumentNullException("header"); 136 | 137 | string[] values = base.GetValues(header); 138 | if (values == null || values.Length == 0) 139 | return null; 140 | 141 | if (split && IsMultiValue(header)) 142 | { 143 | List separated = null; 144 | foreach (var value in values) 145 | { 146 | if (value.IndexOf(',') < 0) 147 | { 148 | if (separated != null) 149 | separated.Add(value); 150 | 151 | continue; 152 | } 153 | 154 | if (separated == null) 155 | { 156 | separated = new List(values.Length + 1); 157 | foreach (var v in values) 158 | { 159 | if (v == value) 160 | break; 161 | 162 | separated.Add(v); 163 | } 164 | } 165 | 166 | var slices = value.Split(','); 167 | var slices_length = slices.Length; 168 | if (value[value.Length - 1] == ',') 169 | --slices_length; 170 | 171 | for (int i = 0; i < slices_length; ++i) 172 | { 173 | separated.Add(slices[i].Trim()); 174 | } 175 | } 176 | 177 | if (separated != null) 178 | return separated.ToArray(); 179 | } 180 | 181 | return values; 182 | } 183 | 184 | public override string[] GetValues(string header) 185 | { 186 | return GetValues_internal(header, true); 187 | } 188 | 189 | public override string[] GetValues(int index) 190 | { 191 | string[] values = base.GetValues(index); 192 | 193 | if (values == null || values.Length == 0) 194 | { 195 | return null; 196 | } 197 | 198 | return values; 199 | } 200 | 201 | public static bool IsRestricted(string headerName) 202 | { 203 | return IsRestricted(headerName, false); 204 | } 205 | 206 | public static bool IsRestricted(string headerName, bool response) 207 | { 208 | if (headerName == null) 209 | throw new ArgumentNullException("headerName"); 210 | 211 | if (headerName.Length == 0) 212 | throw new ArgumentException("empty string", "headerName"); 213 | 214 | if (!IsHeaderName(headerName)) 215 | throw new ArgumentException("Invalid character in header"); 216 | 217 | HeaderInfo info; 218 | if (!headers.TryGetValue(headerName, out info)) 219 | return false; 220 | 221 | var flag = response ? HeaderInfo.Response : HeaderInfo.Request; 222 | return (info & flag) != 0; 223 | } 224 | 225 | public override void Remove(string name) 226 | { 227 | if (name == null) 228 | throw new ArgumentNullException("name"); 229 | 230 | CheckRestrictedHeader(name); 231 | base.Remove(name); 232 | } 233 | 234 | public override void Set(string name, string value) 235 | { 236 | if (name == null) 237 | throw new ArgumentNullException("name"); 238 | if (!IsHeaderName(name)) 239 | throw new ArgumentException("invalid header name"); 240 | if (value == null) 241 | value = String.Empty; 242 | else 243 | value = value.Trim(); 244 | if (!IsHeaderValue(value)) 245 | throw new ArgumentException("invalid header value"); 246 | 247 | CheckRestrictedHeader(name); 248 | base.Set(name, value); 249 | } 250 | 251 | public byte[] ToByteArray() 252 | { 253 | return Encoding.UTF8.GetBytes(ToString()); 254 | } 255 | 256 | internal string ToStringMultiValue() 257 | { 258 | StringBuilder sb = new StringBuilder(); 259 | 260 | int count = base.Count; 261 | for (int i = 0; i < count; i++) 262 | { 263 | string key = GetKey(i); 264 | if (IsMultiValue(key)) 265 | { 266 | foreach (string v in GetValues(i)) 267 | { 268 | sb.Append(key) 269 | .Append(": ") 270 | .Append(v) 271 | .Append("\r\n"); 272 | } 273 | } 274 | else 275 | { 276 | sb.Append(key) 277 | .Append(": ") 278 | .Append(Get(i)) 279 | .Append("\r\n"); 280 | } 281 | } 282 | return sb.Append("\r\n").ToString(); 283 | } 284 | 285 | public override string ToString() 286 | { 287 | StringBuilder sb = new StringBuilder(); 288 | 289 | int count = base.Count; 290 | for (int i = 0; i < count; i++) 291 | sb.Append(GetKey(i)) 292 | .Append(": ") 293 | .Append(Get(i)) 294 | .Append("\r\n"); 295 | 296 | return sb.Append("\r\n").ToString(); 297 | } 298 | 299 | public override string[] AllKeys 300 | { 301 | get 302 | { 303 | return base.AllKeys; 304 | } 305 | } 306 | 307 | public override int Count 308 | { 309 | get 310 | { 311 | return base.Count; 312 | } 313 | } 314 | 315 | public override KeysCollection Keys 316 | { 317 | get 318 | { 319 | return base.Keys; 320 | } 321 | } 322 | 323 | public override string Get(int index) 324 | { 325 | return base.Get(index); 326 | } 327 | 328 | public override string Get(string name) 329 | { 330 | return base.Get(name); 331 | } 332 | 333 | public override string GetKey(int index) 334 | { 335 | return base.GetKey(index); 336 | } 337 | 338 | public override void Clear() 339 | { 340 | base.Clear(); 341 | } 342 | 343 | public override IEnumerator GetEnumerator() 344 | { 345 | return base.GetEnumerator(); 346 | } 347 | 348 | // Internal Methods 349 | 350 | // With this we don't check for invalid characters in header. See bug #55994. 351 | internal void SetInternal(string header) 352 | { 353 | int pos = header.IndexOf(':'); 354 | if (pos == -1) 355 | throw new ArgumentException("no colon found", "header"); 356 | 357 | SetInternal(header.Substring(0, pos), header.Substring(pos + 1)); 358 | } 359 | 360 | internal void SetInternal(string name, string value) 361 | { 362 | if (value == null) 363 | value = String.Empty; 364 | else 365 | value = value.Trim(); 366 | if (!IsHeaderValue(value)) 367 | throw new ArgumentException("invalid header value"); 368 | 369 | if (IsMultiValue(name)) 370 | { 371 | base.Add(name, value); 372 | } 373 | else 374 | { 375 | base.Remove(name); 376 | base.Set(name, value); 377 | } 378 | } 379 | 380 | // Private Methods 381 | 382 | void CheckRestrictedHeader(string headerName) 383 | { 384 | if (!headerRestriction.HasValue) 385 | return; 386 | 387 | HeaderInfo info; 388 | if (!headers.TryGetValue(headerName, out info)) 389 | return; 390 | 391 | if ((info & headerRestriction.Value) != 0) 392 | throw new ArgumentException("This header must be modified with the appropriate property."); 393 | } 394 | 395 | internal static bool IsMultiValue(string headerName) 396 | { 397 | if (headerName == null) 398 | return false; 399 | 400 | HeaderInfo info; 401 | return headers.TryGetValue(headerName, out info) && (info & HeaderInfo.MultiValue) != 0; 402 | } 403 | 404 | internal static bool IsHeaderValue(string value) 405 | { 406 | // TEXT any 8 bit value except CTL's (0-31 and 127) 407 | // but including \r\n space and \t 408 | // after a newline at least one space or \t must follow 409 | // certain header fields allow comments () 410 | 411 | int len = value.Length; 412 | for (int i = 0; i < len; i++) 413 | { 414 | char c = value[i]; 415 | if (c == 127) 416 | return false; 417 | if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t')) 418 | return false; 419 | if (c == '\n' && ++i < len) 420 | { 421 | c = value[i]; 422 | if (c != ' ' && c != '\t') 423 | return false; 424 | } 425 | } 426 | 427 | return true; 428 | } 429 | 430 | internal static bool IsHeaderName(string name) 431 | { 432 | if (name == null || name.Length == 0) 433 | return false; 434 | 435 | int len = name.Length; 436 | for (int i = 0; i < len; i++) 437 | { 438 | char c = name[i]; 439 | if (c > 126 || !allowed_chars[c]) 440 | return false; 441 | } 442 | 443 | return true; 444 | } 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Specialized; 4 | using System.IO; 5 | using System.Net; 6 | using System.Security.Principal; 7 | 8 | namespace SocketHttpListener.Net.WebSockets 9 | { 10 | /// 11 | /// Provides the properties used to access the information in a WebSocket connection request 12 | /// received by the . 13 | /// 14 | /// 15 | /// 16 | public class HttpListenerWebSocketContext : WebSocketContext 17 | { 18 | #region Private Fields 19 | 20 | private HttpListenerContext _context; 21 | private WebSocket _websocket; 22 | 23 | #endregion 24 | 25 | #region Internal Constructors 26 | 27 | internal HttpListenerWebSocketContext( 28 | HttpListenerContext context, string protocol) 29 | { 30 | _context = context; 31 | _websocket = new WebSocket(this, protocol); 32 | } 33 | 34 | #endregion 35 | 36 | #region Internal Properties 37 | 38 | internal Stream Stream 39 | { 40 | get 41 | { 42 | return _context.Connection.Stream; 43 | } 44 | } 45 | 46 | #endregion 47 | 48 | #region Public Properties 49 | 50 | /// 51 | /// Gets the HTTP cookies included in the request. 52 | /// 53 | /// 54 | /// A that contains the cookies. 55 | /// 56 | public override CookieCollection CookieCollection 57 | { 58 | get 59 | { 60 | return _context.Request.Cookies; 61 | } 62 | } 63 | 64 | /// 65 | /// Gets the HTTP headers included in the request. 66 | /// 67 | /// 68 | /// A that contains the headers. 69 | /// 70 | public override NameValueCollection Headers 71 | { 72 | get 73 | { 74 | return _context.Request.Headers; 75 | } 76 | } 77 | 78 | /// 79 | /// Gets the value of the Host header included in the request. 80 | /// 81 | /// 82 | /// A that represents the value of the Host header. 83 | /// 84 | public override string Host 85 | { 86 | get 87 | { 88 | return _context.Request.Headers["Host"]; 89 | } 90 | } 91 | 92 | /// 93 | /// Gets a value indicating whether the client is authenticated. 94 | /// 95 | /// 96 | /// true if the client is authenticated; otherwise, false. 97 | /// 98 | public override bool IsAuthenticated 99 | { 100 | get 101 | { 102 | return _context.Request.IsAuthenticated; 103 | } 104 | } 105 | 106 | /// 107 | /// Gets a value indicating whether the client connected from the local computer. 108 | /// 109 | /// 110 | /// true if the client connected from the local computer; otherwise, false. 111 | /// 112 | public override bool IsLocal 113 | { 114 | get 115 | { 116 | return _context.Request.IsLocal; 117 | } 118 | } 119 | 120 | /// 121 | /// Gets a value indicating whether the WebSocket connection is secured. 122 | /// 123 | /// 124 | /// true if the connection is secured; otherwise, false. 125 | /// 126 | public override bool IsSecureConnection 127 | { 128 | get 129 | { 130 | return _context.Connection.IsSecure; 131 | } 132 | } 133 | 134 | /// 135 | /// Gets a value indicating whether the request is a WebSocket connection request. 136 | /// 137 | /// 138 | /// true if the request is a WebSocket connection request; otherwise, false. 139 | /// 140 | public override bool IsWebSocketRequest 141 | { 142 | get 143 | { 144 | return _context.Request.IsWebSocketRequest; 145 | } 146 | } 147 | 148 | /// 149 | /// Gets the value of the Origin header included in the request. 150 | /// 151 | /// 152 | /// A that represents the value of the Origin header. 153 | /// 154 | public override string Origin 155 | { 156 | get 157 | { 158 | return _context.Request.Headers["Origin"]; 159 | } 160 | } 161 | 162 | /// 163 | /// Gets the query string included in the request. 164 | /// 165 | /// 166 | /// A that contains the query string parameters. 167 | /// 168 | public override NameValueCollection QueryString 169 | { 170 | get 171 | { 172 | return _context.Request.QueryString; 173 | } 174 | } 175 | 176 | /// 177 | /// Gets the URI requested by the client. 178 | /// 179 | /// 180 | /// A that represents the requested URI. 181 | /// 182 | public override Uri RequestUri 183 | { 184 | get 185 | { 186 | return _context.Request.Url; 187 | } 188 | } 189 | 190 | /// 191 | /// Gets the value of the Sec-WebSocket-Key header included in the request. 192 | /// 193 | /// 194 | /// This property provides a part of the information used by the server to prove that it 195 | /// received a valid WebSocket connection request. 196 | /// 197 | /// 198 | /// A that represents the value of the Sec-WebSocket-Key header. 199 | /// 200 | public override string SecWebSocketKey 201 | { 202 | get 203 | { 204 | return _context.Request.Headers["Sec-WebSocket-Key"]; 205 | } 206 | } 207 | 208 | /// 209 | /// Gets the values of the Sec-WebSocket-Protocol header included in the request. 210 | /// 211 | /// 212 | /// This property represents the subprotocols requested by the client. 213 | /// 214 | /// 215 | /// An instance that provides 216 | /// an enumerator which supports the iteration over the values of the Sec-WebSocket-Protocol 217 | /// header. 218 | /// 219 | public override IEnumerable SecWebSocketProtocols 220 | { 221 | get 222 | { 223 | var protocols = _context.Request.Headers["Sec-WebSocket-Protocol"]; 224 | if (protocols != null) 225 | foreach (var protocol in protocols.Split(',')) 226 | yield return protocol.Trim(); 227 | } 228 | } 229 | 230 | /// 231 | /// Gets the value of the Sec-WebSocket-Version header included in the request. 232 | /// 233 | /// 234 | /// This property represents the WebSocket protocol version. 235 | /// 236 | /// 237 | /// A that represents the value of the Sec-WebSocket-Version header. 238 | /// 239 | public override string SecWebSocketVersion 240 | { 241 | get 242 | { 243 | return _context.Request.Headers["Sec-WebSocket-Version"]; 244 | } 245 | } 246 | 247 | /// 248 | /// Gets the server endpoint as an IP address and a port number. 249 | /// 250 | /// 251 | /// A that represents the server endpoint. 252 | /// 253 | public override System.Net.IPEndPoint ServerEndPoint 254 | { 255 | get 256 | { 257 | return _context.Connection.LocalEndPoint; 258 | } 259 | } 260 | 261 | /// 262 | /// Gets the client information (identity, authentication, and security roles). 263 | /// 264 | /// 265 | /// A that represents the client information. 266 | /// 267 | public override IPrincipal User 268 | { 269 | get 270 | { 271 | return _context.User; 272 | } 273 | } 274 | 275 | /// 276 | /// Gets the client endpoint as an IP address and a port number. 277 | /// 278 | /// 279 | /// A that represents the client endpoint. 280 | /// 281 | public override System.Net.IPEndPoint UserEndPoint 282 | { 283 | get 284 | { 285 | return _context.Connection.RemoteEndPoint; 286 | } 287 | } 288 | 289 | /// 290 | /// Gets the instance used for two-way communication 291 | /// between client and server. 292 | /// 293 | /// 294 | /// A . 295 | /// 296 | public override WebSocket WebSocket 297 | { 298 | get 299 | { 300 | return _websocket; 301 | } 302 | } 303 | 304 | #endregion 305 | 306 | #region Internal Methods 307 | 308 | internal void Close() 309 | { 310 | try 311 | { 312 | _context.Connection.Close(true); 313 | } 314 | catch 315 | { 316 | // catch errors sending the closing handshake 317 | } 318 | } 319 | 320 | internal void Close(HttpStatusCode code) 321 | { 322 | _context.Response.Close(code); 323 | } 324 | 325 | #endregion 326 | 327 | #region Public Methods 328 | 329 | /// 330 | /// Returns a that represents the current 331 | /// . 332 | /// 333 | /// 334 | /// A that represents the current 335 | /// . 336 | /// 337 | public override string ToString() 338 | { 339 | return _context.Request.ToString(); 340 | } 341 | 342 | #endregion 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /SocketHttpListener/Net/WebSockets/WebSocketContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Specialized; 4 | using System.Net; 5 | using System.Security.Principal; 6 | 7 | namespace SocketHttpListener.Net.WebSockets 8 | { 9 | /// 10 | /// Exposes the properties used to access the information in a WebSocket connection request. 11 | /// 12 | /// 13 | /// The WebSocketContext class is an abstract class. 14 | /// 15 | public abstract class WebSocketContext 16 | { 17 | #region Protected Constructors 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | protected WebSocketContext () 23 | { 24 | } 25 | 26 | #endregion 27 | 28 | #region Public Properties 29 | 30 | /// 31 | /// Gets the HTTP cookies included in the request. 32 | /// 33 | /// 34 | /// A that contains the cookies. 35 | /// 36 | public abstract CookieCollection CookieCollection { get; } 37 | 38 | /// 39 | /// Gets the HTTP headers included in the request. 40 | /// 41 | /// 42 | /// A that contains the headers. 43 | /// 44 | public abstract NameValueCollection Headers { get; } 45 | 46 | /// 47 | /// Gets the value of the Host header included in the request. 48 | /// 49 | /// 50 | /// A that represents the value of the Host header. 51 | /// 52 | public abstract string Host { get; } 53 | 54 | /// 55 | /// Gets a value indicating whether the client is authenticated. 56 | /// 57 | /// 58 | /// true if the client is authenticated; otherwise, false. 59 | /// 60 | public abstract bool IsAuthenticated { get; } 61 | 62 | /// 63 | /// Gets a value indicating whether the client connected from the local computer. 64 | /// 65 | /// 66 | /// true if the client connected from the local computer; otherwise, false. 67 | /// 68 | public abstract bool IsLocal { get; } 69 | 70 | /// 71 | /// Gets a value indicating whether the WebSocket connection is secured. 72 | /// 73 | /// 74 | /// true if the connection is secured; otherwise, false. 75 | /// 76 | public abstract bool IsSecureConnection { get; } 77 | 78 | /// 79 | /// Gets a value indicating whether the request is a WebSocket connection request. 80 | /// 81 | /// 82 | /// true if the request is a WebSocket connection request; otherwise, false. 83 | /// 84 | public abstract bool IsWebSocketRequest { get; } 85 | 86 | /// 87 | /// Gets the value of the Origin header included in the request. 88 | /// 89 | /// 90 | /// A that represents the value of the Origin header. 91 | /// 92 | public abstract string Origin { get; } 93 | 94 | /// 95 | /// Gets the query string included in the request. 96 | /// 97 | /// 98 | /// A that contains the query string parameters. 99 | /// 100 | public abstract NameValueCollection QueryString { get; } 101 | 102 | /// 103 | /// Gets the URI requested by the client. 104 | /// 105 | /// 106 | /// A that represents the requested URI. 107 | /// 108 | public abstract Uri RequestUri { get; } 109 | 110 | /// 111 | /// Gets the value of the Sec-WebSocket-Key header included in the request. 112 | /// 113 | /// 114 | /// This property provides a part of the information used by the server to prove that it 115 | /// received a valid WebSocket connection request. 116 | /// 117 | /// 118 | /// A that represents the value of the Sec-WebSocket-Key header. 119 | /// 120 | public abstract string SecWebSocketKey { get; } 121 | 122 | /// 123 | /// Gets the values of the Sec-WebSocket-Protocol header included in the request. 124 | /// 125 | /// 126 | /// This property represents the subprotocols requested by the client. 127 | /// 128 | /// 129 | /// An instance that provides 130 | /// an enumerator which supports the iteration over the values of the Sec-WebSocket-Protocol 131 | /// header. 132 | /// 133 | public abstract IEnumerable SecWebSocketProtocols { get; } 134 | 135 | /// 136 | /// Gets the value of the Sec-WebSocket-Version header included in the request. 137 | /// 138 | /// 139 | /// This property represents the WebSocket protocol version. 140 | /// 141 | /// 142 | /// A that represents the value of the Sec-WebSocket-Version header. 143 | /// 144 | public abstract string SecWebSocketVersion { get; } 145 | 146 | /// 147 | /// Gets the server endpoint as an IP address and a port number. 148 | /// 149 | /// 150 | /// A that represents the server endpoint. 151 | /// 152 | public abstract System.Net.IPEndPoint ServerEndPoint { get; } 153 | 154 | /// 155 | /// Gets the client information (identity, authentication, and security roles). 156 | /// 157 | /// 158 | /// A that represents the client information. 159 | /// 160 | public abstract IPrincipal User { get; } 161 | 162 | /// 163 | /// Gets the client endpoint as an IP address and a port number. 164 | /// 165 | /// 166 | /// A that represents the client endpoint. 167 | /// 168 | public abstract System.Net.IPEndPoint UserEndPoint { get; } 169 | 170 | /// 171 | /// Gets the instance used for two-way communication 172 | /// between client and server. 173 | /// 174 | /// 175 | /// A . 176 | /// 177 | public abstract WebSocket WebSocket { get; } 178 | 179 | #endregion 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /SocketHttpListener/Opcode.cs: -------------------------------------------------------------------------------- 1 | namespace SocketHttpListener 2 | { 3 | /// 4 | /// Contains the values of the opcode that indicates the type of a WebSocket frame. 5 | /// 6 | /// 7 | /// The values of the opcode are defined in 8 | /// Section 5.2 of RFC 6455. 9 | /// 10 | public enum Opcode : byte 11 | { 12 | /// 13 | /// Equivalent to numeric value 0. 14 | /// Indicates a continuation frame. 15 | /// 16 | Cont = 0x0, 17 | /// 18 | /// Equivalent to numeric value 1. 19 | /// Indicates a text frame. 20 | /// 21 | Text = 0x1, 22 | /// 23 | /// Equivalent to numeric value 2. 24 | /// Indicates a binary frame. 25 | /// 26 | Binary = 0x2, 27 | /// 28 | /// Equivalent to numeric value 8. 29 | /// Indicates a connection close frame. 30 | /// 31 | Close = 0x8, 32 | /// 33 | /// Equivalent to numeric value 9. 34 | /// Indicates a ping frame. 35 | /// 36 | Ping = 0x9, 37 | /// 38 | /// Equivalent to numeric value 10. 39 | /// Indicates a pong frame. 40 | /// 41 | Pong = 0xa 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /SocketHttpListener/PayloadData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace SocketHttpListener 7 | { 8 | internal class PayloadData : IEnumerable 9 | { 10 | #region Private Fields 11 | 12 | private byte [] _applicationData; 13 | private byte [] _extensionData; 14 | private bool _masked; 15 | 16 | #endregion 17 | 18 | #region Public Const Fields 19 | 20 | public const ulong MaxLength = long.MaxValue; 21 | 22 | #endregion 23 | 24 | #region Public Constructors 25 | 26 | public PayloadData () 27 | : this (new byte [0], new byte [0], false) 28 | { 29 | } 30 | 31 | public PayloadData (byte [] applicationData) 32 | : this (new byte [0], applicationData, false) 33 | { 34 | } 35 | 36 | public PayloadData (string applicationData) 37 | : this (new byte [0], Encoding.UTF8.GetBytes (applicationData), false) 38 | { 39 | } 40 | 41 | public PayloadData (byte [] applicationData, bool masked) 42 | : this (new byte [0], applicationData, masked) 43 | { 44 | } 45 | 46 | public PayloadData (byte [] extensionData, byte [] applicationData, bool masked) 47 | { 48 | _extensionData = extensionData; 49 | _applicationData = applicationData; 50 | _masked = masked; 51 | } 52 | 53 | #endregion 54 | 55 | #region Internal Properties 56 | 57 | internal bool ContainsReservedCloseStatusCode { 58 | get { 59 | return _applicationData.Length > 1 && 60 | _applicationData.SubArray (0, 2).ToUInt16 (ByteOrder.Big).IsReserved (); 61 | } 62 | } 63 | 64 | #endregion 65 | 66 | #region Public Properties 67 | 68 | public byte [] ApplicationData { 69 | get { 70 | return _applicationData; 71 | } 72 | } 73 | 74 | public byte [] ExtensionData { 75 | get { 76 | return _extensionData; 77 | } 78 | } 79 | 80 | public bool IsMasked { 81 | get { 82 | return _masked; 83 | } 84 | } 85 | 86 | public ulong Length { 87 | get { 88 | return (ulong) (_extensionData.LongLength + _applicationData.LongLength); 89 | } 90 | } 91 | 92 | #endregion 93 | 94 | #region Private Methods 95 | 96 | private static void mask (byte [] src, byte [] key) 97 | { 98 | for (long i = 0; i < src.LongLength; i++) 99 | src [i] = (byte) (src [i] ^ key [i % 4]); 100 | } 101 | 102 | #endregion 103 | 104 | #region Public Methods 105 | 106 | public IEnumerator GetEnumerator () 107 | { 108 | foreach (byte b in _extensionData) 109 | yield return b; 110 | 111 | foreach (byte b in _applicationData) 112 | yield return b; 113 | } 114 | 115 | public void Mask (byte [] maskingKey) 116 | { 117 | if (_extensionData.LongLength > 0) 118 | mask (_extensionData, maskingKey); 119 | 120 | if (_applicationData.LongLength > 0) 121 | mask (_applicationData, maskingKey); 122 | 123 | _masked = !_masked; 124 | } 125 | 126 | public byte [] ToByteArray () 127 | { 128 | return _extensionData.LongLength > 0 129 | ? new List (this).ToArray () 130 | : _applicationData; 131 | } 132 | 133 | public override string ToString () 134 | { 135 | return BitConverter.ToString (ToByteArray ()); 136 | } 137 | 138 | #endregion 139 | 140 | #region Explicitly Implemented Interface Members 141 | 142 | IEnumerator IEnumerable.GetEnumerator () 143 | { 144 | return GetEnumerator (); 145 | } 146 | 147 | #endregion 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /SocketHttpListener/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SocketHttpListener")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SocketHttpListener")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("c31d9135-12a5-479d-af58-13fdb9899b5f")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.*")] -------------------------------------------------------------------------------- /SocketHttpListener/Rsv.cs: -------------------------------------------------------------------------------- 1 | namespace SocketHttpListener 2 | { 3 | internal enum Rsv : byte 4 | { 5 | Off = 0x0, 6 | On = 0x1 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /SocketHttpListener/SocketHttpListener.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {5D291CE2-AF6C-4072-9D18-389520C4D869} 8 | Library 9 | Properties 10 | SocketHttpListener 11 | SocketHttpListener 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\packages\Patterns.Logging.1.0.0.6\lib\portable-net45+win8\Patterns.Logging.dll 35 | True 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | if $(ConfigurationName) == Release ( 94 | xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i 95 | ) 96 | 97 | 104 | -------------------------------------------------------------------------------- /SocketHttpListener/WebSocketException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SocketHttpListener 4 | { 5 | /// 6 | /// The exception that is thrown when a gets a fatal error. 7 | /// 8 | public class WebSocketException : Exception 9 | { 10 | #region Internal Constructors 11 | 12 | internal WebSocketException () 13 | : this (CloseStatusCode.Abnormal, null, null) 14 | { 15 | } 16 | 17 | internal WebSocketException (string message) 18 | : this (CloseStatusCode.Abnormal, message, null) 19 | { 20 | } 21 | 22 | internal WebSocketException (CloseStatusCode code) 23 | : this (code, null, null) 24 | { 25 | } 26 | 27 | internal WebSocketException (string message, Exception innerException) 28 | : this (CloseStatusCode.Abnormal, message, innerException) 29 | { 30 | } 31 | 32 | internal WebSocketException (CloseStatusCode code, string message) 33 | : this (code, message, null) 34 | { 35 | } 36 | 37 | internal WebSocketException (CloseStatusCode code, string message, Exception innerException) 38 | : base (message ?? code.GetMessage (), innerException) 39 | { 40 | Code = code; 41 | } 42 | 43 | #endregion 44 | 45 | #region Public Properties 46 | 47 | /// 48 | /// Gets the status code indicating the cause for the exception. 49 | /// 50 | /// 51 | /// One of the enum values, represents the status code indicating 52 | /// the cause for the exception. 53 | /// 54 | public CloseStatusCode Code { 55 | get; private set; 56 | } 57 | 58 | #endregion 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /SocketHttpListener/WebSocketState.cs: -------------------------------------------------------------------------------- 1 | namespace SocketHttpListener 2 | { 3 | /// 4 | /// Contains the values of the state of the WebSocket connection. 5 | /// 6 | /// 7 | /// The values of the state are defined in 8 | /// The WebSocket 9 | /// API. 10 | /// 11 | public enum WebSocketState : ushort 12 | { 13 | /// 14 | /// Equivalent to numeric value 0. 15 | /// Indicates that the connection has not yet been established. 16 | /// 17 | Connecting = 0, 18 | /// 19 | /// Equivalent to numeric value 1. 20 | /// Indicates that the connection is established and the communication is possible. 21 | /// 22 | Open = 1, 23 | /// 24 | /// Equivalent to numeric value 2. 25 | /// Indicates that the connection is going through the closing handshake or 26 | /// the WebSocket.Close method has been invoked. 27 | /// 28 | Closing = 2, 29 | /// 30 | /// Equivalent to numeric value 3. 31 | /// Indicates that the connection has been closed or couldn't be opened. 32 | /// 33 | Closed = 3 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /SocketHttpListener/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/Moq.4.2.1502.0911/Moq.4.2.1502.0911.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaBrowser/SocketHttpListener/60d2266f1b31c476611425147dd56302e2640824/packages/Moq.4.2.1502.0911/Moq.4.2.1502.0911.nupkg -------------------------------------------------------------------------------- /packages/Moq.4.2.1502.0911/lib/net35/Moq.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaBrowser/SocketHttpListener/60d2266f1b31c476611425147dd56302e2640824/packages/Moq.4.2.1502.0911/lib/net35/Moq.dll -------------------------------------------------------------------------------- /packages/Moq.4.2.1502.0911/lib/net40/Moq.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaBrowser/SocketHttpListener/60d2266f1b31c476611425147dd56302e2640824/packages/Moq.4.2.1502.0911/lib/net40/Moq.dll -------------------------------------------------------------------------------- /packages/Moq.4.2.1502.0911/lib/sl4/Moq.Silverlight.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaBrowser/SocketHttpListener/60d2266f1b31c476611425147dd56302e2640824/packages/Moq.4.2.1502.0911/lib/sl4/Moq.Silverlight.dll -------------------------------------------------------------------------------- /packages/Patterns.Logging.1.0.0.6/Patterns.Logging.1.0.0.6.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaBrowser/SocketHttpListener/60d2266f1b31c476611425147dd56302e2640824/packages/Patterns.Logging.1.0.0.6/Patterns.Logging.1.0.0.6.nupkg -------------------------------------------------------------------------------- /packages/Patterns.Logging.1.0.0.6/lib/portable-net45+win8/Patterns.Logging.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaBrowser/SocketHttpListener/60d2266f1b31c476611425147dd56302e2640824/packages/Patterns.Logging.1.0.0.6/lib/portable-net45+win8/Patterns.Logging.dll -------------------------------------------------------------------------------- /packages/WebSocket4Net.0.12/WebSocket4Net.0.12.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaBrowser/SocketHttpListener/60d2266f1b31c476611425147dd56302e2640824/packages/WebSocket4Net.0.12/WebSocket4Net.0.12.nupkg -------------------------------------------------------------------------------- /packages/WebSocket4Net.0.12/lib/monoandroid22/WebSocket4Net.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaBrowser/SocketHttpListener/60d2266f1b31c476611425147dd56302e2640824/packages/WebSocket4Net.0.12/lib/monoandroid22/WebSocket4Net.dll -------------------------------------------------------------------------------- /packages/WebSocket4Net.0.12/lib/monotouch10/WebSocket4Net.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaBrowser/SocketHttpListener/60d2266f1b31c476611425147dd56302e2640824/packages/WebSocket4Net.0.12/lib/monotouch10/WebSocket4Net.dll -------------------------------------------------------------------------------- /packages/WebSocket4Net.0.12/lib/net20/WebSocket4Net.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaBrowser/SocketHttpListener/60d2266f1b31c476611425147dd56302e2640824/packages/WebSocket4Net.0.12/lib/net20/WebSocket4Net.dll -------------------------------------------------------------------------------- /packages/WebSocket4Net.0.12/lib/net35/WebSocket4Net.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaBrowser/SocketHttpListener/60d2266f1b31c476611425147dd56302e2640824/packages/WebSocket4Net.0.12/lib/net35/WebSocket4Net.dll -------------------------------------------------------------------------------- /packages/WebSocket4Net.0.12/lib/net40/WebSocket4Net.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaBrowser/SocketHttpListener/60d2266f1b31c476611425147dd56302e2640824/packages/WebSocket4Net.0.12/lib/net40/WebSocket4Net.dll -------------------------------------------------------------------------------- /packages/WebSocket4Net.0.12/lib/net45/WebSocket4Net.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaBrowser/SocketHttpListener/60d2266f1b31c476611425147dd56302e2640824/packages/WebSocket4Net.0.12/lib/net45/WebSocket4Net.dll -------------------------------------------------------------------------------- /packages/WebSocket4Net.0.12/lib/sl40-windowsphone71/WebSocket4Net.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaBrowser/SocketHttpListener/60d2266f1b31c476611425147dd56302e2640824/packages/WebSocket4Net.0.12/lib/sl40-windowsphone71/WebSocket4Net.dll -------------------------------------------------------------------------------- /packages/WebSocket4Net.0.12/lib/sl40/WebSocket4Net.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaBrowser/SocketHttpListener/60d2266f1b31c476611425147dd56302e2640824/packages/WebSocket4Net.0.12/lib/sl40/WebSocket4Net.dll -------------------------------------------------------------------------------- /packages/WebSocket4Net.0.12/lib/sl50-windowsphone80/WebSocket4Net.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaBrowser/SocketHttpListener/60d2266f1b31c476611425147dd56302e2640824/packages/WebSocket4Net.0.12/lib/sl50-windowsphone80/WebSocket4Net.dll -------------------------------------------------------------------------------- /packages/WebSocket4Net.0.12/lib/sl50/WebSocket4Net.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaBrowser/SocketHttpListener/60d2266f1b31c476611425147dd56302e2640824/packages/WebSocket4Net.0.12/lib/sl50/WebSocket4Net.dll -------------------------------------------------------------------------------- /packages/WebSocket4Net.0.12/nuget.key: -------------------------------------------------------------------------------- 1 | b7c5a19a-abdc-4c4b-8ec9-5fb2283f2069 -------------------------------------------------------------------------------- /packages/repositories.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | --------------------------------------------------------------------------------