diff --git a/Example/bin/Debug/example.exe b/Example/bin/Debug/example.exe
index db440730..3a91d762 100755
Binary files a/Example/bin/Debug/example.exe and b/Example/bin/Debug/example.exe differ
diff --git a/Example/bin/Debug/example.exe.mdb b/Example/bin/Debug/example.exe.mdb
index dfe384b5..f30bf839 100644
Binary files a/Example/bin/Debug/example.exe.mdb and b/Example/bin/Debug/example.exe.mdb differ
diff --git a/Example/bin/Debug/websocket-sharp.dll b/Example/bin/Debug/websocket-sharp.dll
index 0901b46b..3950c3fe 100755
Binary files a/Example/bin/Debug/websocket-sharp.dll and b/Example/bin/Debug/websocket-sharp.dll differ
diff --git a/Example/bin/Debug/websocket-sharp.dll.mdb b/Example/bin/Debug/websocket-sharp.dll.mdb
index 615a3a20..f3c03973 100644
Binary files a/Example/bin/Debug/websocket-sharp.dll.mdb and b/Example/bin/Debug/websocket-sharp.dll.mdb differ
diff --git a/Example/bin/Debug_Ubuntu/example.exe b/Example/bin/Debug_Ubuntu/example.exe
index a9777f9f..63febc5c 100755
Binary files a/Example/bin/Debug_Ubuntu/example.exe and b/Example/bin/Debug_Ubuntu/example.exe differ
diff --git a/Example/bin/Debug_Ubuntu/example.exe.mdb b/Example/bin/Debug_Ubuntu/example.exe.mdb
index 8a27087d..3a9268e1 100644
Binary files a/Example/bin/Debug_Ubuntu/example.exe.mdb and b/Example/bin/Debug_Ubuntu/example.exe.mdb differ
diff --git a/Example/bin/Debug_Ubuntu/websocket-sharp.dll b/Example/bin/Debug_Ubuntu/websocket-sharp.dll
index 764c252c..b2c76b4d 100755
Binary files a/Example/bin/Debug_Ubuntu/websocket-sharp.dll and b/Example/bin/Debug_Ubuntu/websocket-sharp.dll differ
diff --git a/Example/bin/Debug_Ubuntu/websocket-sharp.dll.mdb b/Example/bin/Debug_Ubuntu/websocket-sharp.dll.mdb
index f63a5858..86949826 100644
Binary files a/Example/bin/Debug_Ubuntu/websocket-sharp.dll.mdb and b/Example/bin/Debug_Ubuntu/websocket-sharp.dll.mdb differ
diff --git a/Example/bin/Release/example.exe b/Example/bin/Release/example.exe
index 6fd15adc..19465893 100755
Binary files a/Example/bin/Release/example.exe and b/Example/bin/Release/example.exe differ
diff --git a/Example/bin/Release/websocket-sharp.dll b/Example/bin/Release/websocket-sharp.dll
index cae2760e..d33d5220 100755
Binary files a/Example/bin/Release/websocket-sharp.dll and b/Example/bin/Release/websocket-sharp.dll differ
diff --git a/Example/bin/Release_Ubuntu/example.exe b/Example/bin/Release_Ubuntu/example.exe
index 4199bbd5..c732d6c1 100755
Binary files a/Example/bin/Release_Ubuntu/example.exe and b/Example/bin/Release_Ubuntu/example.exe differ
diff --git a/Example/bin/Release_Ubuntu/websocket-sharp.dll b/Example/bin/Release_Ubuntu/websocket-sharp.dll
index d8d1e088..4160d674 100755
Binary files a/Example/bin/Release_Ubuntu/websocket-sharp.dll and b/Example/bin/Release_Ubuntu/websocket-sharp.dll differ
diff --git a/Example1/bin/Debug/example1.exe b/Example1/bin/Debug/example1.exe
index 17fa2131..6405f7b5 100755
Binary files a/Example1/bin/Debug/example1.exe and b/Example1/bin/Debug/example1.exe differ
diff --git a/Example1/bin/Debug/example1.exe.mdb b/Example1/bin/Debug/example1.exe.mdb
index 5391beb5..f332a487 100644
Binary files a/Example1/bin/Debug/example1.exe.mdb and b/Example1/bin/Debug/example1.exe.mdb differ
diff --git a/Example1/bin/Debug/websocket-sharp.dll b/Example1/bin/Debug/websocket-sharp.dll
index 0901b46b..3950c3fe 100755
Binary files a/Example1/bin/Debug/websocket-sharp.dll and b/Example1/bin/Debug/websocket-sharp.dll differ
diff --git a/Example1/bin/Debug/websocket-sharp.dll.mdb b/Example1/bin/Debug/websocket-sharp.dll.mdb
index 615a3a20..f3c03973 100644
Binary files a/Example1/bin/Debug/websocket-sharp.dll.mdb and b/Example1/bin/Debug/websocket-sharp.dll.mdb differ
diff --git a/Example1/bin/Debug_Ubuntu/example1.exe b/Example1/bin/Debug_Ubuntu/example1.exe
index 4837cbb7..3b7d93cc 100755
Binary files a/Example1/bin/Debug_Ubuntu/example1.exe and b/Example1/bin/Debug_Ubuntu/example1.exe differ
diff --git a/Example1/bin/Debug_Ubuntu/example1.exe.mdb b/Example1/bin/Debug_Ubuntu/example1.exe.mdb
index 5760a7fb..22c62b47 100644
Binary files a/Example1/bin/Debug_Ubuntu/example1.exe.mdb and b/Example1/bin/Debug_Ubuntu/example1.exe.mdb differ
diff --git a/Example1/bin/Debug_Ubuntu/websocket-sharp.dll b/Example1/bin/Debug_Ubuntu/websocket-sharp.dll
index 764c252c..b2c76b4d 100755
Binary files a/Example1/bin/Debug_Ubuntu/websocket-sharp.dll and b/Example1/bin/Debug_Ubuntu/websocket-sharp.dll differ
diff --git a/Example1/bin/Debug_Ubuntu/websocket-sharp.dll.mdb b/Example1/bin/Debug_Ubuntu/websocket-sharp.dll.mdb
index f63a5858..86949826 100644
Binary files a/Example1/bin/Debug_Ubuntu/websocket-sharp.dll.mdb and b/Example1/bin/Debug_Ubuntu/websocket-sharp.dll.mdb differ
diff --git a/Example1/bin/Release/example1.exe b/Example1/bin/Release/example1.exe
index 437b2c97..8721bb86 100755
Binary files a/Example1/bin/Release/example1.exe and b/Example1/bin/Release/example1.exe differ
diff --git a/Example1/bin/Release/websocket-sharp.dll b/Example1/bin/Release/websocket-sharp.dll
index cae2760e..d33d5220 100755
Binary files a/Example1/bin/Release/websocket-sharp.dll and b/Example1/bin/Release/websocket-sharp.dll differ
diff --git a/Example1/bin/Release_Ubuntu/example1.exe b/Example1/bin/Release_Ubuntu/example1.exe
index 5b92a16b..d4dfc94e 100755
Binary files a/Example1/bin/Release_Ubuntu/example1.exe and b/Example1/bin/Release_Ubuntu/example1.exe differ
diff --git a/Example1/bin/Release_Ubuntu/websocket-sharp.dll b/Example1/bin/Release_Ubuntu/websocket-sharp.dll
index d8d1e088..4160d674 100755
Binary files a/Example1/bin/Release_Ubuntu/websocket-sharp.dll and b/Example1/bin/Release_Ubuntu/websocket-sharp.dll differ
diff --git a/Example2/Example2.pidb b/Example2/Example2.pidb
index 4d9246a4..c540fa46 100644
Binary files a/Example2/Example2.pidb and b/Example2/Example2.pidb differ
diff --git a/Example2/bin/Debug/example2.exe b/Example2/bin/Debug/example2.exe
index f649e800..eb52472d 100755
Binary files a/Example2/bin/Debug/example2.exe and b/Example2/bin/Debug/example2.exe differ
diff --git a/Example2/bin/Debug/example2.exe.mdb b/Example2/bin/Debug/example2.exe.mdb
index bc9dee95..ae882dc2 100644
Binary files a/Example2/bin/Debug/example2.exe.mdb and b/Example2/bin/Debug/example2.exe.mdb differ
diff --git a/Example2/bin/Debug/websocket-sharp.dll b/Example2/bin/Debug/websocket-sharp.dll
index 0901b46b..3950c3fe 100755
Binary files a/Example2/bin/Debug/websocket-sharp.dll and b/Example2/bin/Debug/websocket-sharp.dll differ
diff --git a/Example2/bin/Debug/websocket-sharp.dll.mdb b/Example2/bin/Debug/websocket-sharp.dll.mdb
index 615a3a20..f3c03973 100644
Binary files a/Example2/bin/Debug/websocket-sharp.dll.mdb and b/Example2/bin/Debug/websocket-sharp.dll.mdb differ
diff --git a/Example2/bin/Debug_Ubuntu/example2.exe b/Example2/bin/Debug_Ubuntu/example2.exe
index 27c01fbd..a3a0fdd8 100755
Binary files a/Example2/bin/Debug_Ubuntu/example2.exe and b/Example2/bin/Debug_Ubuntu/example2.exe differ
diff --git a/Example2/bin/Debug_Ubuntu/example2.exe.mdb b/Example2/bin/Debug_Ubuntu/example2.exe.mdb
index 4d39e0d2..f707c081 100644
Binary files a/Example2/bin/Debug_Ubuntu/example2.exe.mdb and b/Example2/bin/Debug_Ubuntu/example2.exe.mdb differ
diff --git a/Example2/bin/Debug_Ubuntu/websocket-sharp.dll b/Example2/bin/Debug_Ubuntu/websocket-sharp.dll
index 764c252c..b2c76b4d 100755
Binary files a/Example2/bin/Debug_Ubuntu/websocket-sharp.dll and b/Example2/bin/Debug_Ubuntu/websocket-sharp.dll differ
diff --git a/Example2/bin/Debug_Ubuntu/websocket-sharp.dll.mdb b/Example2/bin/Debug_Ubuntu/websocket-sharp.dll.mdb
index f63a5858..86949826 100644
Binary files a/Example2/bin/Debug_Ubuntu/websocket-sharp.dll.mdb and b/Example2/bin/Debug_Ubuntu/websocket-sharp.dll.mdb differ
diff --git a/Example2/bin/Release/example2.exe b/Example2/bin/Release/example2.exe
index 72fab764..063d17da 100755
Binary files a/Example2/bin/Release/example2.exe and b/Example2/bin/Release/example2.exe differ
diff --git a/Example2/bin/Release/websocket-sharp.dll b/Example2/bin/Release/websocket-sharp.dll
index cae2760e..d33d5220 100755
Binary files a/Example2/bin/Release/websocket-sharp.dll and b/Example2/bin/Release/websocket-sharp.dll differ
diff --git a/Example2/bin/Release_Ubuntu/example2.exe b/Example2/bin/Release_Ubuntu/example2.exe
index 387bc3b7..4ea4e2c1 100755
Binary files a/Example2/bin/Release_Ubuntu/example2.exe and b/Example2/bin/Release_Ubuntu/example2.exe differ
diff --git a/Example2/bin/Release_Ubuntu/websocket-sharp.dll b/Example2/bin/Release_Ubuntu/websocket-sharp.dll
index d8d1e088..4160d674 100755
Binary files a/Example2/bin/Release_Ubuntu/websocket-sharp.dll and b/Example2/bin/Release_Ubuntu/websocket-sharp.dll differ
diff --git a/Example3/App.config b/Example3/App.config
new file mode 100644
index 00000000..c1a9c3b8
--- /dev/null
+++ b/Example3/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/Example3/AssemblyInfo.cs b/Example3/AssemblyInfo.cs
new file mode 100644
index 00000000..c5396f60
--- /dev/null
+++ b/Example3/AssemblyInfo.cs
@@ -0,0 +1,27 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle("Example3")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("sta")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+
+[assembly: AssemblyVersion("1.0.*")]
+
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
diff --git a/Example3/Chat.cs b/Example3/Chat.cs
new file mode 100644
index 00000000..55a14374
--- /dev/null
+++ b/Example3/Chat.cs
@@ -0,0 +1,14 @@
+using System;
+using WebSocketSharp;
+using WebSocketSharp.Server;
+
+namespace Example3
+{
+ public class Chat : WebSocketService
+ {
+ protected override void onMessage(object sender, MessageEventArgs e)
+ {
+ Publish(e.Data);
+ }
+ }
+}
diff --git a/Example3/Echo.cs b/Example3/Echo.cs
new file mode 100644
index 00000000..34c294c9
--- /dev/null
+++ b/Example3/Echo.cs
@@ -0,0 +1,19 @@
+using System;
+using WebSocketSharp;
+using WebSocketSharp.Server;
+
+namespace Example3
+{
+ public class Echo : WebSocketService
+ {
+ protected override void onMessage(object sender, MessageEventArgs e)
+ {
+ Send(e.Data);
+ }
+
+ protected override void onClose(object sender, CloseEventArgs e)
+ {
+ Console.WriteLine("[Echo] Close({0}: {1})", (ushort)e.Code, e.Code);
+ }
+ }
+}
diff --git a/Example3/Example3.csproj b/Example3/Example3.csproj
new file mode 100644
index 00000000..cfb81b7b
--- /dev/null
+++ b/Example3/Example3.csproj
@@ -0,0 +1,76 @@
+
+
+
+ Debug
+ AnyCPU
+ 9.0.21022
+ 2.0
+ {C648BA25-77E5-4A40-A97F-D0AA37B9FB26}
+ Exe
+ Example3
+ Example3
+ v3.5
+
+
+ true
+ full
+ false
+ bin\Debug
+ DEBUG;
+ prompt
+ 4
+ true
+
+
+ none
+ false
+ bin\Release
+ prompt
+ 4
+ true
+
+
+ true
+ full
+ false
+ bin\Debug_Ubuntu
+ DEBUG;
+ prompt
+ 4
+ true
+
+
+ none
+ false
+ bin\Release_Ubuntu
+ prompt
+ 4
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {B357BAC7-529E-4D81-A0D2-71041B19C8DE}
+ websocket-sharp
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Example3/Example3.pidb b/Example3/Example3.pidb
new file mode 100644
index 00000000..820e77cc
Binary files /dev/null and b/Example3/Example3.pidb differ
diff --git a/Example3/Program.cs b/Example3/Program.cs
new file mode 100644
index 00000000..b93b1413
--- /dev/null
+++ b/Example3/Program.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Configuration;
+using System.IO;
+using System.Text;
+using WebSocketSharp;
+using WebSocketSharp.Net;
+using WebSocketSharp.Server;
+
+namespace Example3
+{
+ public class Program
+ {
+ private static HttpServer _httpsv;
+
+ public static void Main(string[] args)
+ {
+ _httpsv = new HttpServer(4649);
+
+ _httpsv.OnResponse += (sender, e) =>
+ {
+ onResponse(e.Context);
+ };
+
+ _httpsv.OnError += (sender, e) =>
+ {
+ Console.WriteLine(e.Message);
+ };
+
+ _httpsv.Start();
+ Console.WriteLine("HTTP Server listening on port: {0}\n", _httpsv.Port);
+
+ Console.WriteLine("Press any key to stop server...");
+ Console.ReadLine();
+
+ _httpsv.Stop();
+ }
+
+ private static byte[] getContent(string path)
+ {
+ if (path == "/")
+ path += "index.html";
+
+ return _httpsv.GetFile(path);
+ }
+
+ private static void onGet(HttpListenerRequest request, HttpListenerResponse response)
+ {
+ var content = getContent(request.RawUrl);
+ if (content != null)
+ {
+ response.WriteContent(content);
+ return;
+ }
+
+ response.StatusCode = (int)HttpStatusCode.NotFound;
+ }
+
+ private static void onResponse(HttpListenerContext context)
+ {
+ var req = context.Request;
+ var res = context.Response;
+
+ if (req.HttpMethod == "GET")
+ {
+ onGet(req, res);
+ return;
+ }
+
+ res.StatusCode = (int)HttpStatusCode.MethodNotAllowed;
+ }
+ }
+}
diff --git a/Example3/Public/Js/echotest.js b/Example3/Public/Js/echotest.js
new file mode 100644
index 00000000..3f61e039
--- /dev/null
+++ b/Example3/Public/Js/echotest.js
@@ -0,0 +1,67 @@
+/**
+ * echotest.js
+ * Derived from Echo Test of WebSocket.org (http://www.websocket.org/echo.html)
+ *
+ * Copyright (c) 2012 Kaazing Corporation.
+ *
+ */
+
+var wsUri = "ws://localhost:4649/";
+var output;
+
+function init(){
+ output = document.getElementById("output");
+ testWebSocket();
+}
+
+function testWebSocket(){
+ websocket = new WebSocket(wsUri);
+
+ websocket.onopen = function(evt){
+ onOpen(evt)
+ };
+
+ websocket.onclose = function(evt){
+ onClose(evt)
+ };
+
+ websocket.onmessage = function(evt){
+ onMessage(evt)
+ };
+
+ websocket.onerror = function(evt){
+ onError(evt)
+ };
+}
+
+function onOpen(evt){
+ writeToScreen("CONNECTED");
+ doSend("WebSocket rocks");
+}
+
+function onClose(evt){
+ writeToScreen("DISCONNECTED");
+}
+
+function onMessage(evt){
+ writeToScreen('RESPONSE: ' + evt.data + '');
+ websocket.close();
+}
+
+function onError(evt){
+ writeToScreen('ERROR: ' + evt.data + '');
+}
+
+function doSend(message){
+ writeToScreen("SENT: " + message);
+ websocket.send(message);
+}
+
+function writeToScreen(message){
+ var pre = document.createElement("p");
+ pre.style.wordWrap = "break-word";
+ pre.innerHTML = message;
+ output.appendChild(pre);
+}
+
+window.addEventListener("load", init, false);
\ No newline at end of file
diff --git a/Example3/Public/index.html b/Example3/Public/index.html
new file mode 100644
index 00000000..8d6fe43c
--- /dev/null
+++ b/Example3/Public/index.html
@@ -0,0 +1,12 @@
+
+
+
+ WebSocket Echo Test
+
+
+
+ WebSocket Echo Test
+
+
+
diff --git a/Example3/bin/Debug/Example3.exe b/Example3/bin/Debug/Example3.exe
new file mode 100755
index 00000000..66dca144
Binary files /dev/null and b/Example3/bin/Debug/Example3.exe differ
diff --git a/Example3/bin/Debug/Example3.exe.config b/Example3/bin/Debug/Example3.exe.config
new file mode 100644
index 00000000..c1a9c3b8
--- /dev/null
+++ b/Example3/bin/Debug/Example3.exe.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/Example3/bin/Debug/Example3.exe.mdb b/Example3/bin/Debug/Example3.exe.mdb
new file mode 100644
index 00000000..6a5a0b11
Binary files /dev/null and b/Example3/bin/Debug/Example3.exe.mdb differ
diff --git a/Example3/bin/Debug/websocket-sharp.dll b/Example3/bin/Debug/websocket-sharp.dll
new file mode 100755
index 00000000..3950c3fe
Binary files /dev/null and b/Example3/bin/Debug/websocket-sharp.dll differ
diff --git a/Example3/bin/Debug/websocket-sharp.dll.mdb b/Example3/bin/Debug/websocket-sharp.dll.mdb
new file mode 100644
index 00000000..f3c03973
Binary files /dev/null and b/Example3/bin/Debug/websocket-sharp.dll.mdb differ
diff --git a/Example3/bin/Debug_Ubuntu/Example3.exe b/Example3/bin/Debug_Ubuntu/Example3.exe
new file mode 100755
index 00000000..9e052a7c
Binary files /dev/null and b/Example3/bin/Debug_Ubuntu/Example3.exe differ
diff --git a/Example3/bin/Debug_Ubuntu/Example3.exe.config b/Example3/bin/Debug_Ubuntu/Example3.exe.config
new file mode 100644
index 00000000..c1a9c3b8
--- /dev/null
+++ b/Example3/bin/Debug_Ubuntu/Example3.exe.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/Example3/bin/Debug_Ubuntu/Example3.exe.mdb b/Example3/bin/Debug_Ubuntu/Example3.exe.mdb
new file mode 100644
index 00000000..958b095a
Binary files /dev/null and b/Example3/bin/Debug_Ubuntu/Example3.exe.mdb differ
diff --git a/Example3/bin/Debug_Ubuntu/websocket-sharp.dll b/Example3/bin/Debug_Ubuntu/websocket-sharp.dll
new file mode 100755
index 00000000..b2c76b4d
Binary files /dev/null and b/Example3/bin/Debug_Ubuntu/websocket-sharp.dll differ
diff --git a/Example3/bin/Debug_Ubuntu/websocket-sharp.dll.mdb b/Example3/bin/Debug_Ubuntu/websocket-sharp.dll.mdb
new file mode 100644
index 00000000..86949826
Binary files /dev/null and b/Example3/bin/Debug_Ubuntu/websocket-sharp.dll.mdb differ
diff --git a/Example3/bin/Release/Example3.exe b/Example3/bin/Release/Example3.exe
new file mode 100755
index 00000000..c21abd53
Binary files /dev/null and b/Example3/bin/Release/Example3.exe differ
diff --git a/Example3/bin/Release/Example3.exe.config b/Example3/bin/Release/Example3.exe.config
new file mode 100644
index 00000000..c1a9c3b8
--- /dev/null
+++ b/Example3/bin/Release/Example3.exe.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/Example3/bin/Release/websocket-sharp.dll b/Example3/bin/Release/websocket-sharp.dll
new file mode 100755
index 00000000..d33d5220
Binary files /dev/null and b/Example3/bin/Release/websocket-sharp.dll differ
diff --git a/Example3/bin/Release_Ubuntu/Example3.exe b/Example3/bin/Release_Ubuntu/Example3.exe
new file mode 100755
index 00000000..cbafb085
Binary files /dev/null and b/Example3/bin/Release_Ubuntu/Example3.exe differ
diff --git a/Example3/bin/Release_Ubuntu/Example3.exe.config b/Example3/bin/Release_Ubuntu/Example3.exe.config
new file mode 100644
index 00000000..c1a9c3b8
--- /dev/null
+++ b/Example3/bin/Release_Ubuntu/Example3.exe.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/Example3/bin/Release_Ubuntu/websocket-sharp.dll b/Example3/bin/Release_Ubuntu/websocket-sharp.dll
new file mode 100755
index 00000000..4160d674
Binary files /dev/null and b/Example3/bin/Release_Ubuntu/websocket-sharp.dll differ
diff --git a/README.md b/README.md
index e9d89d19..f6def7fa 100644
--- a/README.md
+++ b/README.md
@@ -237,6 +237,18 @@ Stopping server.
wssv.Stop();
```
+### HTTP Server with WebSocket ###
+
+I modified System.Net.HttpListener, System.Net.HttpListenerContext and some others of [Mono] to create the HTTP server that the connection can be upgraded to the WebSocket connection if the HTTP server received a WebSocket request.
+
+You can specify the WebSocket service in the same way as the WebSocket server.
+
+```cs
+var httpsv = new HttpServer(4649);
+```
+
+For more information, please refer to the [Example3].
+
## Examples ##
Examples of using **websocket-sharp**.
@@ -253,7 +265,11 @@ Examples of using **websocket-sharp**.
### Example2 ###
-[Example2] starts WebSocket server.
+[Example2] starts the WebSocket server.
+
+### Example3 ###
+
+[Example3] starts the HTTP server that the connection can be upgraded to the WebSocket connection.
## Supported WebSocket Protocol ##
@@ -286,10 +302,12 @@ Licensed under the **[MIT License]**.
[Example]: https://github.com/sta/websocket-sharp/tree/master/Example
[Example1]: https://github.com/sta/websocket-sharp/tree/master/Example1
[Example2]: https://github.com/sta/websocket-sharp/tree/master/Example2
+[Example3]: https://github.com/sta/websocket-sharp/tree/master/Example3
[hixie-75]: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
[hybi-00]: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
[Json.NET]: http://james.newtonking.com/projects/json-net.aspx
[MIT License]: http://www.opensource.org/licenses/mit-license.php
+[Mono]: http://www.mono-project.com/
[RFC 6455]: http://tools.ietf.org/html/rfc6455
[The WebSocket API]: http://dev.w3.org/html5/websockets
[The WebSocket API 日本語訳]: http://www.hcn.zaq.ne.jp/___/WEB/WebSocket-ja.html
diff --git a/websocket-sharp.sln b/websocket-sharp.sln
index ef2da1cf..3c20e06a 100644
--- a/websocket-sharp.sln
+++ b/websocket-sharp.sln
@@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example1", "Example1\Exampl
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example2", "Example2\Example2.csproj", "{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example3", "Example3\Example3.csproj", "{C648BA25-77E5-4A40-A97F-D0AA37B9FB26}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -49,6 +51,14 @@ Global
{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}.Release_Ubuntu|Any CPU.Build.0 = Release_Ubuntu|Any CPU
{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C648BA25-77E5-4A40-A97F-D0AA37B9FB26}.Debug_Ubuntu|Any CPU.ActiveCfg = Debug_Ubuntu|Any CPU
+ {C648BA25-77E5-4A40-A97F-D0AA37B9FB26}.Debug_Ubuntu|Any CPU.Build.0 = Debug_Ubuntu|Any CPU
+ {C648BA25-77E5-4A40-A97F-D0AA37B9FB26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C648BA25-77E5-4A40-A97F-D0AA37B9FB26}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C648BA25-77E5-4A40-A97F-D0AA37B9FB26}.Release_Ubuntu|Any CPU.ActiveCfg = Release_Ubuntu|Any CPU
+ {C648BA25-77E5-4A40-A97F-D0AA37B9FB26}.Release_Ubuntu|Any CPU.Build.0 = Release_Ubuntu|Any CPU
+ {C648BA25-77E5-4A40-A97F-D0AA37B9FB26}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C648BA25-77E5-4A40-A97F-D0AA37B9FB26}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = websocket-sharp\websocket-sharp.csproj
diff --git a/websocket-sharp/CloseEventArgs.cs b/websocket-sharp/CloseEventArgs.cs
index a4ff419f..37c91085 100644
--- a/websocket-sharp/CloseEventArgs.cs
+++ b/websocket-sharp/CloseEventArgs.cs
@@ -69,19 +69,18 @@ namespace WebSocketSharp
public CloseEventArgs(PayloadData data)
: base(Opcode.CLOSE, data)
{
- _code = data.ToBytes().SubArray(0, 2).To(ByteOrder.BIG);
+ _code = (ushort)CloseStatusCode.NO_STATUS_CODE;
+ _reason = String.Empty;
+ _wasClean = false;
+
+ if (data.Length >= 2)
+ _code = data.ToBytes().SubArray(0, 2).To(ByteOrder.BIG);
if (data.Length > 2)
{
var buffer = data.ToBytes().SubArray(2, (int)(data.Length - 2));
_reason = Encoding.UTF8.GetString(buffer);
}
- else
- {
- _reason = String.Empty;
- }
-
- _wasClean = false;
}
}
}
diff --git a/websocket-sharp/Ext.cs b/websocket-sharp/Ext.cs
index 670aa64b..4f50ef7d 100644
--- a/websocket-sharp/Ext.cs
+++ b/websocket-sharp/Ext.cs
@@ -1,11 +1,17 @@
#region MIT License
/**
* Ext.cs
+ * IsPredefinedScheme and MaybeUri methods derived from System.Uri
*
* The MIT License
*
+ * (C) 2001 Garrett Rooney (System.Uri)
+ * (C) 2003 Ian MacLean (System.Uri)
+ * (C) 2003 Ben Maurer (System.Uri)
+ * Copyright (C) 2003,2009 Novell, Inc (http://www.novell.com) (System.Uri)
+ * Copyright (c) 2009 Stephane Delcroix (System.Uri)
* Copyright (c) 2010-2012 sta.blockhead
- *
+ *
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
@@ -28,9 +34,11 @@
using System;
using System.Collections.Generic;
+using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text;
+using WebSocketSharp.Net;
namespace WebSocketSharp
{
@@ -64,6 +72,26 @@ namespace WebSocketSharp
return b == Convert.ToByte(c);
}
+ public static bool Exists(this NameValueCollection headers, string name)
+ {
+ return headers[name] != null
+ ? true
+ : false;
+ }
+
+ public static bool Exists(this NameValueCollection headers, string name, string value)
+ {
+ var values = headers[name];
+ if (values == null)
+ return false;
+
+ foreach (string v in values.Split(','))
+ if (String.Compare(v.Trim(), value, true) == 0)
+ return true;
+
+ return false;
+ }
+
public static string GetHeaderValue(this string src, string separater)
{
int i = src.IndexOf(separater);
@@ -94,6 +122,39 @@ namespace WebSocketSharp
return false;
}
+ // Derived from System.Uri.IsPredefinedScheme method
+ public static bool IsPredefinedScheme(this string scheme)
+ {
+ if (scheme == null && scheme.Length < 3)
+ return false;
+
+ char c = scheme[0];
+
+ if (c == 'h')
+ return (scheme == "http" || scheme == "https");
+
+ if (c == 'f')
+ return (scheme == "file" || scheme == "ftp");
+
+ if (c == 'n')
+ {
+ c = scheme[1];
+
+ if (c == 'e')
+ return (scheme == "news" || scheme == "net.pipe" || scheme == "net.tcp");
+
+ if (scheme == "nntp")
+ return true;
+
+ return false;
+ }
+
+ if ((c == 'g' && scheme == "gopher") || (c == 'm' && scheme == "mailto"))
+ return true;
+
+ return false;
+ }
+
public static bool NotEqualsDo(
this string expected,
string actual,
@@ -111,6 +172,19 @@ namespace WebSocketSharp
return false;
}
+ // Derived from System.Uri.MaybeUri method
+ public static bool MaybeUri(this string uriString)
+ {
+ int p = uriString.IndexOf(':');
+ if (p == -1)
+ return false;
+
+ if (p >= 10)
+ return false;
+
+ return uriString.Substring(0, p).IsPredefinedScheme();
+ }
+
public static byte[] ReadBytes(this TStream stream, ulong length, int bufferLength)
where TStream : System.IO.Stream
{
@@ -320,5 +394,21 @@ namespace WebSocketSharp
return sb.ToString();
}
+
+ public static Uri ToUri(this string uriString)
+ {
+ if (!uriString.MaybeUri())
+ return new Uri(uriString, UriKind.Relative);
+
+ return new Uri(uriString);
+ }
+
+ public static void WriteContent(this HttpListenerResponse response, byte[] content)
+ {
+ var output = response.OutputStream;
+ response.ContentLength64 = content.Length;
+ output.Write(content, 0, content.Length);
+ output.Close();
+ }
}
}
diff --git a/websocket-sharp/Handshake.cs b/websocket-sharp/Handshake.cs
index 6504b44f..577a24ba 100644
--- a/websocket-sharp/Handshake.cs
+++ b/websocket-sharp/Handshake.cs
@@ -29,19 +29,36 @@
using System;
using System.Collections.Specialized;
using System.Text;
+using WebSocketSharp.Net;
namespace WebSocketSharp {
public abstract class Handshake {
+ #region Field
+
protected const string _crlf = "\r\n";
+ #endregion
+
+ #region Constructor
+
protected Handshake()
{
+ ProtocolVersion = HttpVersion.Version11;
+ Headers = new NameValueCollection();
}
- public NameValueCollection Headers { get; protected set; }
- public string Version { get; protected set; }
+ #endregion
+
+ #region Properties
+
+ public NameValueCollection Headers { get; protected set; }
+ public Version ProtocolVersion { get; protected set; }
+
+ #endregion
+
+ #region Methods
public void AddHeader(string name, string value)
{
@@ -55,27 +72,19 @@ namespace WebSocketSharp {
public bool HeaderExists(string name)
{
- return Headers[name] != null
- ? true
- : false;
+ return Headers.Exists(name);
}
public bool HeaderExists(string name, string value)
{
- var values = GetHeaderValues(name);
- if (values == null)
- return false;
-
- foreach (string v in values)
- if (String.Compare(value, v, true) == 0)
- return true;
-
- return false;
+ return Headers.Exists(name, value);
}
public byte[] ToBytes()
{
return Encoding.UTF8.GetBytes(ToString());
}
+
+ #endregion
}
}
diff --git a/websocket-sharp/Net/AuthenticationSchemeSelector.cs b/websocket-sharp/Net/AuthenticationSchemeSelector.cs
new file mode 100644
index 00000000..24e7dd03
--- /dev/null
+++ b/websocket-sharp/Net/AuthenticationSchemeSelector.cs
@@ -0,0 +1,35 @@
+//
+// AuthenticationSchemeSelector.cs
+// Copied from System.Net.AuthenticationSchemeSelector
+//
+// Author:
+// Gonzalo Paniagua Javier
+//
+// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace WebSocketSharp.Net {
+
+ public delegate AuthenticationSchemes AuthenticationSchemeSelector (HttpListenerRequest httpRequest);
+}
diff --git a/websocket-sharp/Net/AuthenticationSchemes.cs b/websocket-sharp/Net/AuthenticationSchemes.cs
new file mode 100644
index 00000000..e5fb7724
--- /dev/null
+++ b/websocket-sharp/Net/AuthenticationSchemes.cs
@@ -0,0 +1,45 @@
+//
+// AuthenticationSchemes.cs
+// Copied from System.Net.AuthenticationSchemes
+//
+// Author:
+// Atsushi Enomoto
+//
+// Copyright (C) 2005 Novell, Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace WebSocketSharp.Net {
+
+ [Flags]
+ public enum AuthenticationSchemes {
+
+ None,
+ Digest = 1,
+ Negotiate = 2,
+ Ntlm = 4,
+ IntegratedWindowsAuthentication = 6,
+ Basic = 8,
+ Anonymous = 0x8000,
+ }
+}
diff --git a/websocket-sharp/Net/ChunkStream.cs b/websocket-sharp/Net/ChunkStream.cs
new file mode 100644
index 00000000..dd31605a
--- /dev/null
+++ b/websocket-sharp/Net/ChunkStream.cs
@@ -0,0 +1,342 @@
+//
+// ChunkStream.cs
+// Copied from System.Net.ChunkStream
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo@ximian.com)
+//
+// (C) 2003 Ximian, Inc (http://www.ximian.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Text;
+
+namespace WebSocketSharp.Net {
+
+ class ChunkStream {
+
+ enum State {
+ None,
+ Body,
+ BodyFinished,
+ Trailer
+ }
+
+ class Chunk {
+ public byte [] Bytes;
+ public int Offset;
+
+ public Chunk (byte [] chunk)
+ {
+ this.Bytes = chunk;
+ }
+
+ public int Read (byte [] buffer, int offset, int size)
+ {
+ int nread = (size > Bytes.Length - Offset) ? Bytes.Length - Offset : size;
+ Buffer.BlockCopy (Bytes, Offset, buffer, offset, nread);
+ Offset += nread;
+ return nread;
+ }
+ }
+
+ internal WebHeaderCollection headers;
+ int chunkSize;
+ int chunkRead;
+ State state;
+ //byte [] waitBuffer;
+ StringBuilder saved;
+ bool sawCR;
+ bool gotit;
+ int trailerState;
+ ArrayList chunks;
+
+ public ChunkStream (byte [] buffer, int offset, int size, WebHeaderCollection headers)
+ : this (headers)
+ {
+ Write (buffer, offset, size);
+ }
+
+ public ChunkStream (WebHeaderCollection headers)
+ {
+ this.headers = headers;
+ saved = new StringBuilder ();
+ chunks = new ArrayList ();
+ chunkSize = -1;
+ }
+
+ public void ResetBuffer ()
+ {
+ chunkSize = -1;
+ chunkRead = 0;
+ chunks.Clear ();
+ }
+
+ public void WriteAndReadBack (byte [] buffer, int offset, int size, ref int read)
+ {
+ if (offset + read > 0)
+ Write (buffer, offset, offset+read);
+ read = Read (buffer, offset, size);
+ }
+
+ public int Read (byte [] buffer, int offset, int size)
+ {
+ return ReadFromChunks (buffer, offset, size);
+ }
+
+ int ReadFromChunks (byte [] buffer, int offset, int size)
+ {
+ int count = chunks.Count;
+ int nread = 0;
+ for (int i = 0; i < count; i++) {
+ Chunk chunk = (Chunk) chunks [i];
+ if (chunk == null)
+ continue;
+
+ if (chunk.Offset == chunk.Bytes.Length) {
+ chunks [i] = null;
+ continue;
+ }
+
+ nread += chunk.Read (buffer, offset + nread, size - nread);
+ if (nread == size)
+ break;
+ }
+
+ return nread;
+ }
+
+ public void Write (byte [] buffer, int offset, int size)
+ {
+ InternalWrite (buffer, ref offset, size);
+ }
+
+ void InternalWrite (byte [] buffer, ref int offset, int size)
+ {
+ if (state == State.None) {
+ state = GetChunkSize (buffer, ref offset, size);
+ if (state == State.None)
+ return;
+
+ saved.Length = 0;
+ sawCR = false;
+ gotit = false;
+ }
+
+ if (state == State.Body && offset < size) {
+ state = ReadBody (buffer, ref offset, size);
+ if (state == State.Body)
+ return;
+ }
+
+ if (state == State.BodyFinished && offset < size) {
+ state = ReadCRLF (buffer, ref offset, size);
+ if (state == State.BodyFinished)
+ return;
+
+ sawCR = false;
+ }
+
+ if (state == State.Trailer && offset < size) {
+ state = ReadTrailer (buffer, ref offset, size);
+ if (state == State.Trailer)
+ return;
+
+ saved.Length = 0;
+ sawCR = false;
+ gotit = false;
+ }
+
+ if (offset < size)
+ InternalWrite (buffer, ref offset, size);
+ }
+
+ public bool WantMore {
+ get { return (chunkRead != chunkSize || chunkSize != 0 || state != State.None); }
+ }
+
+ public int ChunkLeft {
+ get { return chunkSize - chunkRead; }
+ }
+
+ State ReadBody (byte [] buffer, ref int offset, int size)
+ {
+ if (chunkSize == 0)
+ return State.BodyFinished;
+
+ int diff = size - offset;
+ if (diff + chunkRead > chunkSize)
+ diff = chunkSize - chunkRead;
+
+ byte [] chunk = new byte [diff];
+ Buffer.BlockCopy (buffer, offset, chunk, 0, diff);
+ chunks.Add (new Chunk (chunk));
+ offset += diff;
+ chunkRead += diff;
+ return (chunkRead == chunkSize) ? State.BodyFinished : State.Body;
+
+ }
+
+ State GetChunkSize (byte [] buffer, ref int offset, int size)
+ {
+ char c = '\0';
+ while (offset < size) {
+ c = (char) buffer [offset++];
+ if (c == '\r') {
+ if (sawCR)
+ ThrowProtocolViolation ("2 CR found");
+
+ sawCR = true;
+ continue;
+ }
+
+ if (sawCR && c == '\n')
+ break;
+
+ if (c == ' ')
+ gotit = true;
+
+ if (!gotit)
+ saved.Append (c);
+
+ if (saved.Length > 20)
+ ThrowProtocolViolation ("chunk size too long.");
+ }
+
+ if (!sawCR || c != '\n') {
+ if (offset < size)
+ ThrowProtocolViolation ("Missing \\n");
+
+ try {
+ if (saved.Length > 0) {
+ chunkSize = Int32.Parse (RemoveChunkExtension (saved.ToString ()), NumberStyles.HexNumber);
+ }
+ } catch (Exception) {
+ ThrowProtocolViolation ("Cannot parse chunk size.");
+ }
+
+ return State.None;
+ }
+
+ chunkRead = 0;
+ try {
+ chunkSize = Int32.Parse (RemoveChunkExtension (saved.ToString ()), NumberStyles.HexNumber);
+ } catch (Exception) {
+ ThrowProtocolViolation ("Cannot parse chunk size.");
+ }
+
+ if (chunkSize == 0) {
+ trailerState = 2;
+ return State.Trailer;
+ }
+
+ return State.Body;
+ }
+
+ static string RemoveChunkExtension (string input)
+ {
+ int idx = input.IndexOf (';');
+ if (idx == -1)
+ return input;
+ return input.Substring (0, idx);
+ }
+
+ State ReadCRLF (byte [] buffer, ref int offset, int size)
+ {
+ if (!sawCR) {
+ if ((char) buffer [offset++] != '\r')
+ ThrowProtocolViolation ("Expecting \\r");
+
+ sawCR = true;
+ if (offset == size)
+ return State.BodyFinished;
+ }
+
+ if (sawCR && (char) buffer [offset++] != '\n')
+ ThrowProtocolViolation ("Expecting \\n");
+
+ return State.None;
+ }
+
+ State ReadTrailer (byte [] buffer, ref int offset, int size)
+ {
+ char c = '\0';
+
+ // short path
+ if (trailerState == 2 && (char) buffer [offset] == '\r' && saved.Length == 0) {
+ offset++;
+ if (offset < size && (char) buffer [offset] == '\n') {
+ offset++;
+ return State.None;
+ }
+ offset--;
+ }
+
+ int st = trailerState;
+ string stString = "\r\n\r";
+ while (offset < size && st < 4) {
+ c = (char) buffer [offset++];
+ if ((st == 0 || st == 2) && c == '\r') {
+ st++;
+ continue;
+ }
+
+ if ((st == 1 || st == 3) && c == '\n') {
+ st++;
+ continue;
+ }
+
+ if (st > 0) {
+ saved.Append (stString.Substring (0, saved.Length == 0? st-2: st));
+ st = 0;
+ if (saved.Length > 4196)
+ ThrowProtocolViolation ("Error reading trailer (too long).");
+ }
+ }
+
+ if (st < 4) {
+ trailerState = st;
+ if (offset < size)
+ ThrowProtocolViolation ("Error reading trailer.");
+
+ return State.Trailer;
+ }
+
+ StringReader reader = new StringReader (saved.ToString ());
+ string line;
+ while ((line = reader.ReadLine ()) != null && line != "")
+ headers.Add (line);
+
+ return State.None;
+ }
+
+ static void ThrowProtocolViolation (string message)
+ {
+ WebException we = new WebException (message, null, WebExceptionStatus.ServerProtocolViolation, null);
+ throw we;
+ }
+ }
+}
diff --git a/websocket-sharp/Net/ChunkedInputStream.cs b/websocket-sharp/Net/ChunkedInputStream.cs
new file mode 100644
index 00000000..df988bc0
--- /dev/null
+++ b/websocket-sharp/Net/ChunkedInputStream.cs
@@ -0,0 +1,181 @@
+//
+// ChunkedInputStream.cs
+// Copied from System.Net.ChunkedInputStream
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo@novell.com)
+//
+// Copyright (c) 2005 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+
+namespace WebSocketSharp.Net {
+
+ class ChunkedInputStream : RequestStream
+ {
+ HttpListenerContext context;
+ ChunkStream decoder;
+ bool disposed;
+ bool no_more_data;
+
+ class ReadBufferState {
+
+ public HttpStreamAsyncResult Ares;
+ public byte [] Buffer;
+ public int Count;
+ public int InitialCount;
+ public int Offset;
+
+ public ReadBufferState (
+ byte [] buffer, int offset, int count, HttpStreamAsyncResult ares)
+ {
+ Buffer = buffer;
+ Offset = offset;
+ Count = count;
+ InitialCount = count;
+ Ares = ares;
+ }
+ }
+
+ public ChunkedInputStream (
+ HttpListenerContext context, Stream stream, byte [] buffer, int offset, int length)
+ : base (stream, buffer, offset, length)
+ {
+ this.context = context;
+ WebHeaderCollection coll = (WebHeaderCollection) context.Request.Headers;
+ decoder = new ChunkStream (coll);
+ }
+
+ public ChunkStream Decoder {
+ get { return decoder; }
+ set { decoder = value; }
+ }
+
+ void OnRead (IAsyncResult base_ares)
+ {
+ ReadBufferState rb = (ReadBufferState) base_ares.AsyncState;
+ HttpStreamAsyncResult ares = rb.Ares;
+ try {
+ int nread = base.EndRead (base_ares);
+ decoder.Write (ares.Buffer, ares.Offset, nread);
+ nread = decoder.Read (rb.Buffer, rb.Offset, rb.Count);
+ rb.Offset += nread;
+ rb.Count -= nread;
+ if (rb.Count == 0 || !decoder.WantMore || nread == 0) {
+ no_more_data = !decoder.WantMore && nread == 0;
+ ares.Count = rb.InitialCount - rb.Count;
+ ares.Complete ();
+ return;
+ }
+ ares.Offset = 0;
+ ares.Count = Math.Min (8192, decoder.ChunkLeft + 6);
+ base.BeginRead (ares.Buffer, ares.Offset, ares.Count, OnRead, rb);
+ } catch (Exception e) {
+ context.Connection.SendError (e.Message, 400);
+ ares.Complete (e);
+ }
+ }
+
+ public override IAsyncResult BeginRead (
+ byte [] buffer, int offset, int count, AsyncCallback cback, object state)
+ {
+ if (disposed)
+ throw new ObjectDisposedException (GetType ().ToString ());
+
+ if (buffer == null)
+ throw new ArgumentNullException ("buffer");
+
+ int len = buffer.Length;
+ if (offset < 0 || offset > len)
+ throw new ArgumentOutOfRangeException ("offset exceeds the size of buffer");
+
+ if (count < 0 || offset > len - count)
+ throw new ArgumentOutOfRangeException ("offset+size exceeds the size of buffer");
+
+ HttpStreamAsyncResult ares = new HttpStreamAsyncResult ();
+ ares.Callback = cback;
+ ares.State = state;
+ if (no_more_data) {
+ ares.Complete ();
+ return ares;
+ }
+ int nread = decoder.Read (buffer, offset, count);
+ offset += nread;
+ count -= nread;
+ if (count == 0) {
+ // got all we wanted, no need to bother the decoder yet
+ ares.Count = nread;
+ ares.Complete ();
+ return ares;
+ }
+ if (!decoder.WantMore) {
+ no_more_data = nread == 0;
+ ares.Count = nread;
+ ares.Complete ();
+ return ares;
+ }
+ ares.Buffer = new byte [8192];
+ ares.Offset = 0;
+ ares.Count = 8192;
+ ReadBufferState rb = new ReadBufferState (buffer, offset, count, ares);
+ rb.InitialCount += nread;
+ base.BeginRead (ares.Buffer, ares.Offset, ares.Count, OnRead, rb);
+ return ares;
+ }
+
+ public override void Close ()
+ {
+ if (!disposed) {
+ disposed = true;
+ base.Close ();
+ }
+ }
+
+ public override int EndRead (IAsyncResult ares)
+ {
+ if (disposed)
+ throw new ObjectDisposedException (GetType ().ToString ());
+
+ HttpStreamAsyncResult my_ares = ares as HttpStreamAsyncResult;
+ if (ares == null)
+ throw new ArgumentException ("Invalid IAsyncResult", "ares");
+
+ if (!ares.IsCompleted)
+ ares.AsyncWaitHandle.WaitOne ();
+
+ if (my_ares.Error != null)
+ throw new HttpListenerException (400, "I/O operation aborted.");
+
+ return my_ares.Count;
+ }
+
+ public override int Read ([In,Out] byte [] buffer, int offset, int count)
+ {
+ IAsyncResult ares = BeginRead (buffer, offset, count, null, null);
+ return EndRead (ares);
+ }
+ }
+}
diff --git a/websocket-sharp/Net/Cookie.cs b/websocket-sharp/Net/Cookie.cs
new file mode 100644
index 00000000..74a1d077
--- /dev/null
+++ b/websocket-sharp/Net/Cookie.cs
@@ -0,0 +1,350 @@
+//
+// Cookie.cs
+// Copied from System.Net.Cookie
+//
+// Authors:
+// Lawrence Pit (loz@cable.a2000.nl)
+// Gonzalo Paniagua Javier (gonzalo@ximian.com)
+// Daniel Nauck (dna@mono-project.de)
+// Sebastien Pouliot (sebastien@ximian.com)
+//
+// Copyright (C) 2004,2009 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Text;
+using System.Globalization;
+using System.Collections;
+using System.Net;
+
+namespace WebSocketSharp.Net {
+
+ // Supported cookie formats are:
+ // Netscape: http://home.netscape.com/newsref/std/cookie_spec.html
+ // RFC 2109: http://www.ietf.org/rfc/rfc2109.txt
+ // RFC 2965: http://www.ietf.org/rfc/rfc2965.txt
+ [Serializable]
+ public sealed class Cookie
+ {
+ string comment;
+ Uri commentUri;
+ bool discard;
+ string domain;
+ DateTime expires;
+ bool httpOnly;
+ string name;
+ string path;
+ string port;
+ int [] ports;
+ bool secure;
+ DateTime timestamp;
+ string val;
+ int version;
+
+ static char [] reservedCharsName = new char [] {' ', '=', ';', ',', '\n', '\r', '\t'};
+ static char [] portSeparators = new char [] {'"', ','};
+ static string tspecials = "()<>@,;:\\\"/[]?={} \t"; // from RFC 2965, 2068
+
+ public Cookie ()
+ {
+ expires = DateTime.MinValue;
+ timestamp = DateTime.Now;
+ domain = String.Empty;
+ name = String.Empty;
+ val = String.Empty;
+ comment = String.Empty;
+ port = String.Empty;
+ }
+
+ public Cookie (string name, string value)
+ : this ()
+ {
+ Name = name;
+ Value = value;
+ }
+
+ public Cookie (string name, string value, string path)
+ : this (name, value)
+ {
+ Path = path;
+ }
+
+ public Cookie (string name, string value, string path, string domain)
+ : this (name, value, path)
+ {
+ Domain = domain;
+ }
+
+ public string Comment {
+ get { return comment; }
+ set { comment = value == null ? String.Empty : value; }
+ }
+
+ public Uri CommentUri {
+ get { return commentUri; }
+ set { commentUri = value; }
+ }
+
+ public bool Discard {
+ get { return discard; }
+ set { discard = value; }
+ }
+
+ public string Domain {
+ get { return domain; }
+ set {
+ if (String.IsNullOrEmpty (value)) {
+ domain = String.Empty;
+ ExactDomain = true;
+ } else {
+ domain = value;
+ ExactDomain = (value [0] != '.');
+ }
+ }
+ }
+
+ internal bool ExactDomain { get; set; }
+
+ public bool Expired {
+ get {
+ return expires <= DateTime.Now &&
+ expires != DateTime.MinValue;
+ }
+ set {
+ if (value)
+ expires = DateTime.Now;
+ }
+ }
+
+ public DateTime Expires {
+ get { return expires; }
+ set { expires = value; }
+ }
+
+ public bool HttpOnly {
+ get { return httpOnly; }
+ set { httpOnly = value; }
+ }
+
+ public string Name {
+ get { return name; }
+ set {
+ if (String.IsNullOrEmpty (value))
+ throw new CookieException ("Name cannot be empty");
+
+ if (value [0] == '$' || value.IndexOfAny (reservedCharsName) != -1) {
+ // see CookieTest, according to MS implementation
+ // the name value changes even though it's incorrect
+ name = String.Empty;
+ throw new CookieException ("Name contains invalid characters");
+ }
+
+ name = value;
+ }
+ }
+
+ public string Path {
+ get { return (path == null) ? String.Empty : path; }
+ set { path = (value == null) ? String.Empty : value; }
+ }
+
+ public string Port {
+ get { return port; }
+ set {
+ if (String.IsNullOrEmpty (value)) {
+ port = String.Empty;
+ return;
+ }
+ if (value [0] != '"' || value [value.Length - 1] != '"') {
+ throw new CookieException("The 'Port'='" + value + "' part of the cookie is invalid. Port must be enclosed by double quotes.");
+ }
+ port = value;
+ string [] values = port.Split (portSeparators);
+ ports = new int[values.Length];
+ for (int i = 0; i < ports.Length; i++) {
+ ports [i] = Int32.MinValue;
+ if (values [i].Length == 0)
+ continue;
+ try {
+ ports [i] = Int32.Parse (values [i]);
+ } catch (Exception e) {
+ throw new CookieException("The 'Port'='" + value + "' part of the cookie is invalid. Invalid value: " + values [i], e);
+ }
+ }
+ Version = 1;
+ }
+ }
+
+ internal int [] Ports {
+ get { return ports; }
+ }
+
+ public bool Secure {
+ get { return secure; }
+ set { secure = value; }
+ }
+
+ public DateTime TimeStamp {
+ get { return timestamp; }
+ }
+
+ public string Value {
+ get { return val; }
+ set {
+ if (value == null) {
+ val = String.Empty;
+ return;
+ }
+
+ // LAMESPEC: According to .Net specs the Value property should not accept
+ // the semicolon and comma characters, yet it does. For now we'll follow
+ // the behaviour of MS.Net instead of the specs.
+ /*
+ if (value.IndexOfAny(reservedCharsValue) != -1)
+ throw new CookieException("Invalid value. Value cannot contain semicolon or comma characters.");
+ */
+
+ val = value;
+ }
+ }
+
+ public int Version {
+ get { return version; }
+ set {
+ if ((value < 0) || (value > 10))
+ version = 0;
+ else
+ version = value;
+ }
+ }
+
+ public override bool Equals (Object obj)
+ {
+ System.Net.Cookie c = obj as System.Net.Cookie;
+
+ return c != null &&
+ String.Compare (this.name, c.Name, true, CultureInfo.InvariantCulture) == 0 &&
+ String.Compare (this.val, c.Value, false, CultureInfo.InvariantCulture) == 0 &&
+ String.Compare (this.Path, c.Path, false, CultureInfo.InvariantCulture) == 0 &&
+ String.Compare (this.domain, c.Domain, true, CultureInfo.InvariantCulture) == 0 &&
+ this.version == c.Version;
+ }
+
+ public override int GetHashCode ()
+ {
+ return hash (
+ StringComparer.InvariantCultureIgnoreCase.GetHashCode (name),
+ val.GetHashCode (),
+ Path.GetHashCode (),
+ StringComparer.InvariantCultureIgnoreCase.GetHashCode (domain),
+ version);
+ }
+
+ private static int hash (int i, int j, int k, int l, int m)
+ {
+ return i ^ (j << 13 | j >> 19) ^ (k << 26 | k >> 6) ^ (l << 7 | l >> 25) ^ (m << 20 | m >> 12);
+ }
+
+ // returns a string that can be used to send a cookie to an Origin Server
+ // i.e., only used for clients
+ // see para 4.2.2 of RFC 2109 and para 3.3.4 of RFC 2965
+ // see also bug #316017
+ public override string ToString ()
+ {
+ return ToString (null);
+ }
+
+ internal string ToString (Uri uri)
+ {
+ if (name.Length == 0)
+ return String.Empty;
+
+ StringBuilder result = new StringBuilder (64);
+
+ if (version > 0)
+ result.Append ("$Version=").Append (version).Append ("; ");
+
+ result.Append (name).Append ("=").Append (val);
+
+ if (version == 0)
+ return result.ToString ();
+
+ if (!String.IsNullOrEmpty (path))
+ result.Append ("; $Path=").Append (path);
+ else if (uri != null)
+ result.Append ("; $Path=/").Append (path);
+
+ bool append_domain = (uri == null) || (uri.Host != domain);
+ if (append_domain && !String.IsNullOrEmpty (domain))
+ result.Append ("; $Domain=").Append (domain);
+
+ if (port != null && port.Length != 0)
+ result.Append ("; $Port=").Append (port);
+
+ return result.ToString ();
+ }
+
+ internal string ToClientString ()
+ {
+ if (name.Length == 0)
+ return String.Empty;
+
+ StringBuilder result = new StringBuilder (64);
+
+ if (version > 0)
+ result.Append ("Version=").Append (version).Append (";");
+
+ result.Append (name).Append ("=").Append (val);
+
+ if (path != null && path.Length != 0)
+ result.Append (";Path=").Append (QuotedString (path));
+
+ if (domain != null && domain.Length != 0)
+ result.Append (";Domain=").Append (QuotedString (domain));
+
+ if (port != null && port.Length != 0)
+ result.Append (";Port=").Append (port);
+
+ return result.ToString ();
+ }
+
+ // See par 3.6 of RFC 2616
+ string QuotedString (string value)
+ {
+ if (version == 0 || IsToken (value))
+ return value;
+ else
+ return "\"" + value.Replace("\"", "\\\"") + "\"";
+ }
+
+ bool IsToken (string value)
+ {
+ int len = value.Length;
+ for (int i = 0; i < len; i++) {
+ char c = value [i];
+ if (c < 0x20 || c >= 0x7f || tspecials.IndexOf (c) != -1)
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/websocket-sharp/Net/CookieCollection.cs b/websocket-sharp/Net/CookieCollection.cs
new file mode 100644
index 00000000..cf2a501b
--- /dev/null
+++ b/websocket-sharp/Net/CookieCollection.cs
@@ -0,0 +1,176 @@
+//
+// CookieCollection.cs
+// Copied from System.Net.CookieCollection
+//
+// Authors:
+// Lawrence Pit (loz@cable.a2000.nl)
+// Gonzalo Paniagua Javier (gonzalo@ximian.com)
+// Sebastien Pouliot
+//
+// Copyright (C) 2004,2009 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Runtime.Serialization;
+
+namespace WebSocketSharp.Net {
+
+ [Serializable]
+ public class CookieCollection : ICollection, IEnumerable
+ {
+ // not 100% identical to MS implementation
+ sealed class CookieCollectionComparer : IComparer
+ {
+ public int Compare (Cookie x, Cookie y)
+ {
+ if (x == null || y == null)
+ return 0;
+
+ int c1 = x.Name.Length + x.Value.Length;
+ int c2 = y.Name.Length + y.Value.Length;
+
+ return (c1 - c2);
+ }
+ }
+
+ static CookieCollectionComparer Comparer = new CookieCollectionComparer ();
+
+ List list = new List ();
+
+ internal IList List {
+ get { return list; }
+ }
+ // ICollection
+ public int Count {
+ get { return list.Count; }
+ }
+
+ public bool IsSynchronized {
+ get { return false; }
+ }
+
+ public Object SyncRoot {
+ get { return this; }
+ }
+
+ public void CopyTo (Array array, int index)
+ {
+ (list as IList).CopyTo (array, index);
+ }
+
+ public void CopyTo (Cookie [] array, int index)
+ {
+ list.CopyTo (array, index);
+ }
+
+ // IEnumerable
+ public IEnumerator GetEnumerator ()
+ {
+ return list.GetEnumerator ();
+ }
+
+ // This
+
+ // LAMESPEC: So how is one supposed to create a writable CookieCollection
+ // instance?? We simply ignore this property, as this collection is always
+ // writable.
+ public bool IsReadOnly {
+ get { return true; }
+ }
+
+ public void Add (Cookie cookie)
+ {
+ if (cookie == null)
+ throw new ArgumentNullException ("cookie");
+
+ int pos = SearchCookie (cookie);
+ if (pos == -1)
+ list.Add (cookie);
+ else
+ list [pos] = cookie;
+ }
+
+ internal void Sort ()
+ {
+ if (list.Count > 0)
+ list.Sort (Comparer);
+ }
+
+ int SearchCookie (Cookie cookie)
+ {
+ string name = cookie.Name;
+ string domain = cookie.Domain;
+ string path = cookie.Path;
+
+ for (int i = list.Count - 1; i >= 0; i--) {
+ Cookie c = list [i];
+ if (c.Version != cookie.Version)
+ continue;
+
+ if (0 != String.Compare (domain, c.Domain, true, CultureInfo.InvariantCulture))
+ continue;
+
+ if (0 != String.Compare (name, c.Name, true, CultureInfo.InvariantCulture))
+ continue;
+
+ if (0 != String.Compare (path, c.Path, true, CultureInfo.InvariantCulture))
+ continue;
+
+ return i;
+ }
+
+ return -1;
+ }
+
+ public void Add (CookieCollection cookies)
+ {
+ if (cookies == null)
+ throw new ArgumentNullException ("cookies");
+
+ foreach (Cookie c in cookies)
+ Add (c);
+ }
+
+ public Cookie this [int index] {
+ get {
+ if (index < 0 || index >= list.Count)
+ throw new ArgumentOutOfRangeException ("index");
+
+ return list [index];
+ }
+ }
+
+ public Cookie this [string name] {
+ get {
+ foreach (Cookie c in list) {
+ if (0 == String.Compare (c.Name, name, true, CultureInfo.InvariantCulture))
+ return c;
+ }
+ return null;
+ }
+ }
+ }
+}
diff --git a/websocket-sharp/Net/CookieException.cs b/websocket-sharp/Net/CookieException.cs
new file mode 100644
index 00000000..24f39661
--- /dev/null
+++ b/websocket-sharp/Net/CookieException.cs
@@ -0,0 +1,69 @@
+//
+// CookieException.cs
+// Copied from System.Net.CookieException
+//
+// Author:
+// Lawrence Pit (loz@cable.a2000.nl)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Globalization;
+using System.Runtime.Serialization;
+
+namespace WebSocketSharp.Net {
+
+ [Serializable]
+ public class CookieException : FormatException, ISerializable
+ {
+ // Constructors
+ public CookieException ()
+ : base ()
+ {
+ }
+
+ internal CookieException (string msg)
+ : base (msg)
+ {
+ }
+
+ internal CookieException (string msg, Exception e)
+ : base (msg, e)
+ {
+ }
+
+ protected CookieException (SerializationInfo info, StreamingContext context)
+ : base (info, context)
+ {
+ }
+
+ // Methods
+ void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData (info, context);
+ }
+
+ public override void GetObjectData (SerializationInfo serializationInfo, StreamingContext streamingContext)
+ {
+ base.GetObjectData (serializationInfo, streamingContext);
+ }
+ }
+}
diff --git a/websocket-sharp/Net/EndPointListener.cs b/websocket-sharp/Net/EndPointListener.cs
new file mode 100644
index 00000000..7865d016
--- /dev/null
+++ b/websocket-sharp/Net/EndPointListener.cs
@@ -0,0 +1,404 @@
+//
+// EndPointListener.cs
+// Copied from System.Net.EndPointListener
+//
+// Author:
+// Gonzalo Paniagua Javier (gonzalo@novell.com)
+//
+// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+// Copyright (c) 2012 sta.blockhead (sta.blockhead@gmail.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+
+namespace WebSocketSharp.Net {
+
+ sealed class EndPointListener {
+
+ #region Fields
+
+ List all; // host = '+'
+ X509Certificate2 cert;
+ IPEndPoint endpoint;
+ AsymmetricAlgorithm key;
+ Dictionary prefixes;
+ bool secure;
+ Socket sock;
+ List unhandled; // host = '*'
+ Hashtable unregistered;
+
+ #endregion
+
+ #region Constructor
+
+ public EndPointListener (IPAddress addr, int port, bool secure)
+ {
+ if (secure) {
+ this.secure = secure;
+ LoadCertificateAndKey (addr, port);
+ }
+
+ endpoint = new IPEndPoint (addr, port);
+ sock = new Socket (addr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+ sock.Bind (endpoint);
+ sock.Listen (500);
+ var args = new SocketAsyncEventArgs ();
+ args.UserToken = this;
+ args.Completed += OnAccept;
+ sock.AcceptAsync (args);
+ prefixes = new Dictionary ();
+ unregistered = Hashtable.Synchronized (new Hashtable ());
+ }
+
+ #endregion
+
+ #region Private Static Methods
+
+ static void OnAccept (object sender, EventArgs e)
+ {
+ SocketAsyncEventArgs args = (SocketAsyncEventArgs) e;
+ EndPointListener epl = (EndPointListener) args.UserToken;
+ Socket accepted = null;
+ if (args.SocketError == SocketError.Success) {
+ accepted = args.AcceptSocket;
+ args.AcceptSocket = null;
+ }
+
+ try {
+ if (epl.sock != null)
+ epl.sock.AcceptAsync (args);
+ } catch {
+ if (accepted != null) {
+ try {
+ accepted.Close ();
+ } catch {}
+ accepted = null;
+ }
+ }
+
+ if (accepted == null)
+ return;
+
+ if (epl.secure && (epl.cert == null || epl.key == null)) {
+ accepted.Close ();
+ return;
+ }
+ HttpConnection conn = new HttpConnection (accepted, epl, epl.secure, epl.cert, epl.key);
+ epl.unregistered [conn] = conn;
+ conn.BeginReadRequest ();
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ void AddSpecial (List coll, ListenerPrefix prefix)
+ {
+ if (coll == null)
+ return;
+
+ foreach (ListenerPrefix p in coll) {
+ if (p.Path == prefix.Path) // TODO: code
+ throw new HttpListenerException (400, "Prefix already in use.");
+ }
+ coll.Add (prefix);
+ }
+
+ void CheckIfRemove ()
+ {
+ if (prefixes.Count > 0)
+ return;
+
+ var list = unhandled;
+ if (list != null && list.Count > 0)
+ return;
+
+ list = all;
+ if (list != null && list.Count > 0)
+ return;
+
+ EndPointManager.RemoveEndPoint (this, endpoint);
+ }
+
+ RSACryptoServiceProvider CreateRSAFromFile (string filename)
+ {
+ if (filename == null)
+ throw new ArgumentNullException ("filename");
+
+ var rsa = new RSACryptoServiceProvider ();
+ byte[] pvk = null;
+ using (FileStream fs = File.Open (filename, FileMode.Open, FileAccess.Read, FileShare.Read))
+ {
+ pvk = new byte [fs.Length];
+ fs.Read (pvk, 0, pvk.Length);
+ }
+ rsa.ImportCspBlob (pvk);
+ return rsa;
+ }
+
+ void LoadCertificateAndKey (IPAddress addr, int port)
+ {
+ // Actually load the certificate
+ try {
+ string dirname = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData);
+ string path = Path.Combine (dirname, ".mono");
+ path = Path.Combine (path, "httplistener");
+ string cert_file = Path.Combine (path, String.Format ("{0}.cer", port));
+ string pvk_file = Path.Combine (path, String.Format ("{0}.pvk", port));
+ cert = new X509Certificate2 (cert_file);
+ key = CreateRSAFromFile (pvk_file);
+ } catch {
+ // ignore errors
+ }
+ }
+
+ HttpListener MatchFromList (
+ string host, string path, List list, out ListenerPrefix prefix)
+ {
+ prefix = null;
+ if (list == null)
+ return null;
+
+ HttpListener best_match = null;
+ int best_length = -1;
+
+ foreach (ListenerPrefix p in list) {
+ string ppath = p.Path;
+ if (ppath.Length < best_length)
+ continue;
+
+ if (path.StartsWith (ppath)) {
+ best_length = ppath.Length;
+ best_match = p.Listener;
+ prefix = p;
+ }
+ }
+
+ return best_match;
+ }
+
+ bool RemoveSpecial (List coll, ListenerPrefix prefix)
+ {
+ if (coll == null)
+ return false;
+
+ int c = coll.Count;
+ for (int i = 0; i < c; i++) {
+ ListenerPrefix p = coll [i];
+ if (p.Path == prefix.Path) {
+ coll.RemoveAt (i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ HttpListener SearchListener (Uri uri, out ListenerPrefix prefix)
+ {
+ prefix = null;
+ if (uri == null)
+ return null;
+
+ string host = uri.Host;
+ int port = uri.Port;
+ string path = HttpUtility.UrlDecode (uri.AbsolutePath);
+ string path_slash = path [path.Length - 1] == '/' ? path : path + "/";
+
+ HttpListener best_match = null;
+ int best_length = -1;
+
+ if (host != null && host != "") {
+ var p_ro = prefixes;
+ foreach (ListenerPrefix p in p_ro.Keys) {
+ string ppath = p.Path;
+ if (ppath.Length < best_length)
+ continue;
+
+ if (p.Host != host || p.Port != port)
+ continue;
+
+ if (path.StartsWith (ppath) || path_slash.StartsWith (ppath)) {
+ best_length = ppath.Length;
+ best_match = p_ro [p];
+ prefix = p;
+ }
+ }
+ if (best_length != -1)
+ return best_match;
+ }
+
+ var list = unhandled;
+ best_match = MatchFromList (host, path, list, out prefix);
+ if (path != path_slash && best_match == null)
+ best_match = MatchFromList (host, path_slash, list, out prefix);
+ if (best_match != null)
+ return best_match;
+
+ list = all;
+ best_match = MatchFromList (host, path, list, out prefix);
+ if (path != path_slash && best_match == null)
+ best_match = MatchFromList (host, path_slash, list, out prefix);
+ if (best_match != null)
+ return best_match;
+
+ return null;
+ }
+
+ #endregion
+
+ #region Internal Method
+
+ internal void RemoveConnection (HttpConnection conn)
+ {
+ unregistered.Remove (conn);
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public void AddPrefix (ListenerPrefix prefix, HttpListener listener)
+ {
+ List current;
+ List future;
+ if (prefix.Host == "*") {
+ do {
+ current = unhandled;
+ future = (current != null)
+ ? new List (current)
+ : new List ();
+ prefix.Listener = listener;
+ AddSpecial (future, prefix);
+ } while (Interlocked.CompareExchange (ref unhandled, future, current) != current);
+ return;
+ }
+
+ if (prefix.Host == "+") {
+ do {
+ current = all;
+ future = (current != null)
+ ? new List (current)
+ : new List ();
+ prefix.Listener = listener;
+ AddSpecial (future, prefix);
+ } while (Interlocked.CompareExchange (ref all, future, current) != current);
+ return;
+ }
+
+ Dictionary prefs, p2;
+ do {
+ prefs = prefixes;
+ if (prefs.ContainsKey (prefix)) {
+ HttpListener other = prefs [prefix];
+ if (other != listener) // TODO: code.
+ throw new HttpListenerException (400, "There's another listener for " + prefix);
+ return;
+ }
+ p2 = new Dictionary (prefs);
+ p2 [prefix] = listener;
+ } while (Interlocked.CompareExchange (ref prefixes, p2, prefs) != prefs);
+ }
+
+ public bool BindContext (HttpListenerContext context)
+ {
+ HttpListenerRequest req = context.Request;
+ ListenerPrefix prefix;
+ HttpListener listener = SearchListener (req.Url, out prefix);
+ if (listener == null)
+ return false;
+
+ context.Listener = listener;
+ context.Connection.Prefix = prefix;
+ return true;
+ }
+
+ public void Close ()
+ {
+ sock.Close ();
+ lock (unregistered.SyncRoot) {
+ foreach (HttpConnection c in unregistered.Keys)
+ c.Close (true);
+ unregistered.Clear ();
+ }
+ }
+
+ public void RemovePrefix (ListenerPrefix prefix, HttpListener listener)
+ {
+ List current;
+ List future;
+ if (prefix.Host == "*") {
+ do {
+ current = unhandled;
+ future = (current != null)
+ ? new List (current)
+ : new List ();
+ if (!RemoveSpecial (future, prefix))
+ break; // Prefix not found
+ } while (Interlocked.CompareExchange (ref unhandled, future, current) != current);
+ CheckIfRemove ();
+ return;
+ }
+
+ if (prefix.Host == "+") {
+ do {
+ current = all;
+ future = (current != null)
+ ? new List (current)
+ : new List ();
+ if (!RemoveSpecial (future, prefix))
+ break; // Prefix not found
+ } while (Interlocked.CompareExchange (ref all, future, current) != current);
+ CheckIfRemove ();
+ return;
+ }
+
+ Dictionary prefs, p2;
+ do {
+ prefs = prefixes;
+ if (!prefs.ContainsKey (prefix))
+ break;
+
+ p2 = new Dictionary (prefs);
+ p2.Remove (prefix);
+ } while (Interlocked.CompareExchange (ref prefixes, p2, prefs) != prefs);
+ CheckIfRemove ();
+ }
+
+ public void UnbindContext (HttpListenerContext context)
+ {
+ if (context == null || context.Request == null)
+ return;
+
+ context.Listener.UnregisterContext (context);
+ }
+
+ #endregion
+ }
+}
diff --git a/websocket-sharp/Net/EndPointManager.cs b/websocket-sharp/Net/EndPointManager.cs
new file mode 100644
index 00000000..af62fd56
--- /dev/null
+++ b/websocket-sharp/Net/EndPointManager.cs
@@ -0,0 +1,148 @@
+//
+// EndPointManager.cs
+// Copied from System.Net.EndPointManager
+//
+// Author:
+// Gonzalo Paniagua Javier (gonzalo@ximian.com)
+//
+// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+// Copyright (c) 2012 sta.blockhead (sta.blockhead@gmail.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Net;
+
+namespace WebSocketSharp.Net {
+
+ sealed class EndPointManager {
+
+ static Dictionary> ip_to_endpoints = new Dictionary> ();
+
+ private EndPointManager ()
+ {
+ }
+
+ static void AddPrefixInternal (string p, HttpListener listener)
+ {
+ ListenerPrefix lp = new ListenerPrefix (p);
+ if (lp.Path.IndexOf ('%') != -1)
+ throw new HttpListenerException (400, "Invalid path.");
+
+ if (lp.Path.IndexOf ("//", StringComparison.Ordinal) != -1) // TODO: Code?
+ throw new HttpListenerException (400, "Invalid path.");
+
+ // Always listens on all the interfaces, no matter the host name/ip used.
+ EndPointListener epl = GetEPListener (IPAddress.Any, lp.Port, listener, lp.Secure);
+ epl.AddPrefix (lp, listener);
+ }
+
+ static EndPointListener GetEPListener (IPAddress addr, int port, HttpListener listener, bool secure)
+ {
+ Dictionary p = null;
+ if (ip_to_endpoints.ContainsKey (addr)) {
+ p = ip_to_endpoints [addr];
+ } else {
+ p = new Dictionary ();
+ ip_to_endpoints [addr] = p;
+ }
+
+ EndPointListener epl = null;
+ if (p.ContainsKey (port)) {
+ epl = p [port];
+ } else {
+ epl = new EndPointListener (addr, port, secure);
+ p [port] = epl;
+ }
+
+ return epl;
+ }
+
+ static void RemovePrefixInternal (string prefix, HttpListener listener)
+ {
+ ListenerPrefix lp = new ListenerPrefix (prefix);
+ if (lp.Path.IndexOf ('%') != -1)
+ return;
+
+ if (lp.Path.IndexOf ("//", StringComparison.Ordinal) != -1)
+ return;
+
+ EndPointListener epl = GetEPListener (IPAddress.Any, lp.Port, listener, lp.Secure);
+ epl.RemovePrefix (lp, listener);
+ }
+
+ public static void AddListener (HttpListener listener)
+ {
+ List added = new List ();
+ try {
+ lock (((ICollection)ip_to_endpoints).SyncRoot) {
+ foreach (string prefix in listener.Prefixes) {
+ AddPrefixInternal (prefix, listener);
+ added.Add (prefix);
+ }
+ }
+ } catch {
+ foreach (string prefix in added) {
+ RemovePrefix (prefix, listener);
+ }
+ throw;
+ }
+ }
+
+ public static void AddPrefix (string prefix, HttpListener listener)
+ {
+ lock (((ICollection)ip_to_endpoints).SyncRoot) {
+ AddPrefixInternal (prefix, listener);
+ }
+ }
+
+ public static void RemoveEndPoint (EndPointListener epl, IPEndPoint ep)
+ {
+ lock (((ICollection)ip_to_endpoints).SyncRoot) {
+ Dictionary p = null;
+ p = ip_to_endpoints [ep.Address];
+ p.Remove (ep.Port);
+ if (p.Count == 0) {
+ ip_to_endpoints.Remove (ep.Address);
+ }
+ epl.Close ();
+ }
+ }
+
+ public static void RemoveListener (HttpListener listener)
+ {
+ lock (((ICollection)ip_to_endpoints).SyncRoot) {
+ foreach (string prefix in listener.Prefixes) {
+ RemovePrefixInternal (prefix, listener);
+ }
+ }
+ }
+
+ public static void RemovePrefix (string prefix, HttpListener listener)
+ {
+ lock (((ICollection)ip_to_endpoints).SyncRoot) {
+ RemovePrefixInternal (prefix, listener);
+ }
+ }
+ }
+}
diff --git a/websocket-sharp/Net/HttpConnection.cs b/websocket-sharp/Net/HttpConnection.cs
new file mode 100644
index 00000000..44f56449
--- /dev/null
+++ b/websocket-sharp/Net/HttpConnection.cs
@@ -0,0 +1,522 @@
+//
+// HttpConnection.cs
+// Copied from System.Net.HttpConnection
+//
+// Author:
+// Gonzalo Paniagua Javier (gonzalo@novell.com)
+//
+// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+// Copyright (c) 2012 sta.blockhead (sta.blockhead@gmail.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading;
+
+namespace WebSocketSharp.Net {
+
+ sealed class HttpConnection {
+
+ #region Enums
+
+ enum InputState {
+ RequestLine,
+ Headers
+ }
+
+ enum LineState {
+ None,
+ CR,
+ LF
+ }
+
+ #endregion
+
+ #region Private Const Field
+
+ const int BufferSize = 8192;
+
+ #endregion
+
+ #region Private Static Field
+
+ static AsyncCallback onread_cb = new AsyncCallback (OnRead);
+
+ #endregion
+
+ #region Private Fields
+
+ byte [] buffer;
+ bool chunked;
+ HttpListenerContext context;
+ bool context_bound;
+ StringBuilder current_line;
+ EndPointListener epl;
+ InputState input_state;
+ RequestStream i_stream;
+ AsymmetricAlgorithm key;
+ HttpListener last_listener;
+ LineState line_state;
+// IPEndPoint local_ep; // never used
+ MemoryStream ms;
+ ResponseStream o_stream;
+ int position;
+ ListenerPrefix prefix;
+ int reuses;
+ bool secure;
+ Socket sock;
+ Stream stream;
+ int s_timeout;
+ Timer timer;
+
+ #endregion
+
+ #region Constructor
+
+ public HttpConnection (
+ Socket sock,
+ EndPointListener epl,
+ bool secure,
+ X509Certificate2 cert,
+ AsymmetricAlgorithm key
+ )
+ {
+ this.sock = sock;
+ this.epl = epl;
+ this.secure = secure;
+ this.key = key;
+// if (secure == false) {
+// stream = new NetworkStream (sock, false);
+// } else {
+// var ssl_stream = new SslServerStream (new NetworkStream (sock, false), cert, false, false);
+// ssl_stream.PrivateKeyCertSelectionDelegate += OnPVKSelection;
+// stream = ssl_stream;
+// }
+ var net_stream = new NetworkStream (sock, false);
+ if (!secure) {
+ stream = net_stream;
+ } else {
+ var ssl_stream = new SslStream(net_stream);
+ ssl_stream.AuthenticateAsServer(cert);
+ stream = ssl_stream;
+ }
+ timer = new Timer (OnTimeout, null, Timeout.Infinite, Timeout.Infinite);
+ Init ();
+ }
+
+ #endregion
+
+ #region Properties
+
+ public bool IsClosed {
+ get { return (sock == null); }
+ }
+
+ public bool IsSecure {
+ get { return secure; }
+ }
+
+ public IPEndPoint LocalEndPoint {
+ get { return (IPEndPoint) sock.LocalEndPoint; }
+ }
+
+ public ListenerPrefix Prefix {
+ get { return prefix; }
+ set { prefix = value; }
+ }
+
+ public IPEndPoint RemoteEndPoint {
+ get { return (IPEndPoint) sock.RemoteEndPoint; }
+ }
+
+ public int Reuses {
+ get { return reuses; }
+ }
+
+ public Stream Stream {
+ get { return stream; }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ void CloseSocket ()
+ {
+ if (sock == null)
+ return;
+
+ try {
+ sock.Close ();
+ } catch {
+ } finally {
+ sock = null;
+ }
+ RemoveConnection ();
+ }
+
+ void Init ()
+ {
+ context_bound = false;
+ i_stream = null;
+ o_stream = null;
+ prefix = null;
+ chunked = false;
+ ms = new MemoryStream ();
+ position = 0;
+ input_state = InputState.RequestLine;
+ line_state = LineState.None;
+ context = new HttpListenerContext (this);
+ s_timeout = 90000; // 90k ms for first request, 15k ms from then on
+ }
+
+ AsymmetricAlgorithm OnPVKSelection (X509Certificate certificate, string targetHost)
+ {
+ return key;
+ }
+
+ static void OnRead (IAsyncResult ares)
+ {
+ HttpConnection cnc = (HttpConnection) ares.AsyncState;
+ cnc.OnReadInternal (ares);
+ }
+
+ void OnReadInternal (IAsyncResult ares)
+ {
+ timer.Change (Timeout.Infinite, Timeout.Infinite);
+ int nread = -1;
+ try {
+ nread = stream.EndRead (ares);
+ ms.Write (buffer, 0, nread);
+ if (ms.Length > 32768) {
+ SendError ("Bad request", 400);
+ Close (true);
+ return;
+ }
+ } catch {
+ if (ms != null && ms.Length > 0)
+ SendError ();
+ if (sock != null) {
+ CloseSocket ();
+ Unbind ();
+ }
+ return;
+ }
+
+ if (nread == 0) {
+ //if (ms.Length > 0)
+ // SendError (); // Why bother?
+ CloseSocket ();
+ Unbind ();
+ return;
+ }
+
+ if (ProcessInput (ms)) {
+ if (!context.HaveError)
+ context.Request.FinishInitialization ();
+
+ if (context.HaveError) {
+ SendError ();
+ Close (true);
+ return;
+ }
+
+ if (!epl.BindContext (context)) {
+ SendError ("Invalid host", 400);
+ Close (true);
+ return;
+ }
+ HttpListener listener = context.Listener;
+ if (last_listener != listener) {
+ RemoveConnection ();
+ listener.AddConnection (this);
+ last_listener = listener;
+ }
+
+ context_bound = true;
+ listener.RegisterContext (context);
+ return;
+ }
+ stream.BeginRead (buffer, 0, BufferSize, onread_cb, this);
+ }
+
+ void OnTimeout (object unused)
+ {
+ CloseSocket ();
+ Unbind ();
+ }
+
+ // true -> done processing
+ // false -> need more input
+ bool ProcessInput (MemoryStream ms)
+ {
+ byte [] buffer = ms.GetBuffer ();
+ int len = (int) ms.Length;
+ int used = 0;
+ string line;
+
+ try {
+ line = ReadLine (buffer, position, len - position, ref used);
+ position += used;
+ } catch {
+ context.ErrorMessage = "Bad request";
+ context.ErrorStatus = 400;
+ return true;
+ }
+
+ do {
+ if (line == null)
+ break;
+ if (line == "") {
+ if (input_state == InputState.RequestLine)
+ continue;
+ current_line = null;
+ ms = null;
+ return true;
+ }
+
+ if (input_state == InputState.RequestLine) {
+ context.Request.SetRequestLine (line);
+ input_state = InputState.Headers;
+ } else {
+ try {
+ context.Request.AddHeader (line);
+ } catch (Exception e) {
+ context.ErrorMessage = e.Message;
+ context.ErrorStatus = 400;
+ return true;
+ }
+ }
+
+ if (context.HaveError)
+ return true;
+
+ if (position >= len)
+ break;
+ try {
+ line = ReadLine (buffer, position, len - position, ref used);
+ position += used;
+ } catch {
+ context.ErrorMessage = "Bad request";
+ context.ErrorStatus = 400;
+ return true;
+ }
+ } while (line != null);
+
+ if (used == len) {
+ ms.SetLength (0);
+ position = 0;
+ }
+ return false;
+ }
+
+ string ReadLine (byte [] buffer, int offset, int len, ref int used)
+ {
+ if (current_line == null)
+ current_line = new StringBuilder ();
+ int last = offset + len;
+ used = 0;
+ for (int i = offset; i < last && line_state != LineState.LF; i++) {
+ used++;
+ byte b = buffer [i];
+ if (b == 13) {
+ line_state = LineState.CR;
+ } else if (b == 10) {
+ line_state = LineState.LF;
+ } else {
+ current_line.Append ((char) b);
+ }
+ }
+
+ string result = null;
+ if (line_state == LineState.LF) {
+ line_state = LineState.None;
+ result = current_line.ToString ();
+ current_line.Length = 0;
+ }
+
+ return result;
+ }
+
+ void RemoveConnection ()
+ {
+ if (last_listener == null)
+ epl.RemoveConnection (this);
+ else
+ last_listener.RemoveConnection (this);
+ }
+
+ void Unbind ()
+ {
+ if (context_bound) {
+ epl.UnbindContext (context);
+ context_bound = false;
+ }
+ }
+
+ #endregion
+
+ #region Internal Method
+
+ internal void Close (bool force_close)
+ {
+ if (sock != null) {
+ Stream st = GetResponseStream ();
+ st.Close ();
+ o_stream = null;
+ }
+
+ if (sock != null) {
+ force_close |= !context.Request.KeepAlive;
+ if (!force_close)
+ force_close = (context.Response.Headers ["connection"] == "close");
+ /*
+ if (!force_close) {
+// bool conn_close = (status_code == 400 || status_code == 408 || status_code == 411 ||
+// status_code == 413 || status_code == 414 || status_code == 500 ||
+// status_code == 503);
+
+ force_close |= (context.Request.ProtocolVersion <= HttpVersion.Version10);
+ }
+ */
+
+ if (!force_close && context.Request.FlushInput ()) {
+ if (chunked && context.Response.ForceCloseChunked == false) {
+ // Don't close. Keep working.
+ reuses++;
+ Unbind ();
+ Init ();
+ BeginReadRequest ();
+ return;
+ }
+
+ reuses++;
+ Unbind ();
+ Init ();
+ BeginReadRequest ();
+ return;
+ }
+
+ Socket s = sock;
+ sock = null;
+ try {
+ if (s != null)
+ s.Shutdown (SocketShutdown.Both);
+ } catch {
+ } finally {
+ if (s != null)
+ s.Close ();
+ }
+ Unbind ();
+ RemoveConnection ();
+ return;
+ }
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public void BeginReadRequest ()
+ {
+ if (buffer == null)
+ buffer = new byte [BufferSize];
+ try {
+ if (reuses == 1)
+ s_timeout = 15000;
+ timer.Change (s_timeout, Timeout.Infinite);
+ stream.BeginRead (buffer, 0, BufferSize, onread_cb, this);
+ } catch {
+ timer.Change (Timeout.Infinite, Timeout.Infinite);
+ CloseSocket ();
+ Unbind ();
+ }
+ }
+
+ public void Close ()
+ {
+ Close (false);
+ }
+
+ public RequestStream GetRequestStream (bool chunked, long contentlength)
+ {
+ if (i_stream == null) {
+ byte [] buffer = ms.GetBuffer ();
+ int length = (int) ms.Length;
+ ms = null;
+ if (chunked) {
+ this.chunked = true;
+ context.Response.SendChunked = true;
+ i_stream = new ChunkedInputStream (context, stream, buffer, position, length - position);
+ } else {
+ i_stream = new RequestStream (stream, buffer, position, length - position, contentlength);
+ }
+ }
+ return i_stream;
+ }
+
+ public ResponseStream GetResponseStream ()
+ {
+ // TODO: can we get this stream before reading the input?
+ if (o_stream == null) {
+ HttpListener listener = context.Listener;
+ bool ign = (listener == null) ? true : listener.IgnoreWriteExceptions;
+ o_stream = new ResponseStream (stream, context.Response, ign);
+ }
+ return o_stream;
+ }
+
+ public void SendError ()
+ {
+ SendError (context.ErrorMessage, context.ErrorStatus);
+ }
+
+ public void SendError (string msg, int status)
+ {
+ try {
+ HttpListenerResponse response = context.Response;
+ response.StatusCode = status;
+ response.ContentType = "text/html";
+ string description = HttpListenerResponse.GetStatusDescription (status);
+ string str;
+ if (msg != null)
+ str = String.Format ("{0} ({1})
", description, msg);
+ else
+ str = String.Format ("{0}
", description);
+
+ byte [] error = context.Response.ContentEncoding.GetBytes (str);
+ response.Close (error, false);
+ } catch {
+ // response was already closed
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/websocket-sharp/Net/HttpListener.cs b/websocket-sharp/Net/HttpListener.cs
new file mode 100644
index 00000000..d568f3a5
--- /dev/null
+++ b/websocket-sharp/Net/HttpListener.cs
@@ -0,0 +1,372 @@
+//
+// HttpListener.cs
+// Copied from System.Net.HttpListener
+//
+// Author:
+// Gonzalo Paniagua Javier (gonzalo@novell.com)
+//
+// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+// Copyright (c) 2012 sta.blockhead (sta.blockhead@gmail.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Net;
+using System.Threading;
+
+// TODO: logging
+namespace WebSocketSharp.Net {
+
+ public sealed class HttpListener : IDisposable {
+
+ #region Fields
+
+ AuthenticationSchemes auth_schemes;
+ AuthenticationSchemeSelector auth_selector;
+ Dictionary connections;
+ List ctx_queue;
+ bool disposed;
+ bool ignore_write_exceptions;
+ bool listening;
+ HttpListenerPrefixCollection prefixes;
+ string realm;
+ Dictionary registry;
+ bool unsafe_ntlm_auth;
+ List wait_queue;
+
+ #endregion
+
+ #region Constructor
+
+ public HttpListener ()
+ {
+ prefixes = new HttpListenerPrefixCollection (this);
+ registry = new Dictionary ();
+ connections = new Dictionary ();
+ ctx_queue = new List ();
+ wait_queue = new List ();
+ auth_schemes = AuthenticationSchemes.Anonymous;
+ }
+
+ #endregion
+
+ #region Properties
+
+ // TODO: Digest, NTLM and Negotiate require ControlPrincipal
+ public AuthenticationSchemes AuthenticationSchemes {
+ get { return auth_schemes; }
+ set {
+ CheckDisposed ();
+ auth_schemes = value;
+ }
+ }
+
+ public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate {
+ get { return auth_selector; }
+ set {
+ CheckDisposed ();
+ auth_selector = value;
+ }
+ }
+
+ public bool IgnoreWriteExceptions {
+ get { return ignore_write_exceptions; }
+ set {
+ CheckDisposed ();
+ ignore_write_exceptions = value;
+ }
+ }
+
+ public bool IsListening {
+ get { return listening; }
+ }
+
+ public static bool IsSupported {
+ get { return true; }
+ }
+
+ public HttpListenerPrefixCollection Prefixes {
+ get {
+ CheckDisposed ();
+ return prefixes;
+ }
+ }
+
+ // TODO: Use this
+ public string Realm {
+ get { return realm; }
+ set {
+ CheckDisposed ();
+ realm = value;
+ }
+ }
+
+ // TODO: Support for NTLM needs some loving.
+ public bool UnsafeConnectionNtlmAuthentication {
+ get { return unsafe_ntlm_auth; }
+ set {
+ CheckDisposed ();
+ unsafe_ntlm_auth = value;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ void Cleanup (bool close_existing)
+ {
+ lock (((ICollection)registry).SyncRoot) {
+ if (close_existing) {
+ // Need to copy this since closing will call UnregisterContext
+ ICollection keys = registry.Keys;
+ var all = new HttpListenerContext [keys.Count];
+ keys.CopyTo (all, 0);
+ registry.Clear ();
+ for (int i = all.Length - 1; i >= 0; i--)
+ all [i].Connection.Close (true);
+ }
+
+ lock (((ICollection)connections).SyncRoot) {
+ ICollection keys = connections.Keys;
+ var conns = new HttpConnection [keys.Count];
+ keys.CopyTo (conns, 0);
+ connections.Clear ();
+ for (int i = conns.Length - 1; i >= 0; i--)
+ conns [i].Close (true);
+ }
+
+ lock (((ICollection)ctx_queue).SyncRoot) {
+ var ctxs = ctx_queue.ToArray ();
+ ctx_queue.Clear ();
+ for (int i = ctxs.Length - 1; i >= 0; i--)
+ ctxs [i].Connection.Close (true);
+ }
+
+ lock (((ICollection)wait_queue).SyncRoot) {
+ Exception exc = new ObjectDisposedException ("listener");
+ foreach (ListenerAsyncResult ares in wait_queue) {
+ ares.Complete (exc);
+ }
+ wait_queue.Clear ();
+ }
+ }
+ }
+
+ void Close (bool force)
+ {
+ CheckDisposed ();
+ EndPointManager.RemoveListener (this);
+ Cleanup (force);
+ }
+
+ // Must be called with a lock on ctx_queue
+ HttpListenerContext GetContextFromQueue ()
+ {
+ if (ctx_queue.Count == 0)
+ return null;
+
+ var context = ctx_queue [0];
+ ctx_queue.RemoveAt (0);
+ return context;
+ }
+
+ void IDisposable.Dispose ()
+ {
+ if (disposed)
+ return;
+
+ Close (true); //TODO: Should we force here or not?
+ disposed = true;
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal void AddConnection (HttpConnection cnc)
+ {
+ connections [cnc] = cnc;
+ }
+
+ internal void CheckDisposed ()
+ {
+ if (disposed)
+ throw new ObjectDisposedException (GetType ().ToString ());
+ }
+
+ internal void RegisterContext (HttpListenerContext context)
+ {
+ lock (((ICollection)registry).SyncRoot)
+ registry [context] = context;
+
+ ListenerAsyncResult ares = null;
+ lock (((ICollection)wait_queue).SyncRoot) {
+ if (wait_queue.Count == 0) {
+ lock (((ICollection)ctx_queue).SyncRoot)
+ ctx_queue.Add (context);
+ } else {
+ ares = wait_queue [0];
+ wait_queue.RemoveAt (0);
+ }
+ }
+ if (ares != null)
+ ares.Complete (context);
+ }
+
+ internal void RemoveConnection (HttpConnection cnc)
+ {
+ connections.Remove (cnc);
+ }
+
+ internal AuthenticationSchemes SelectAuthenticationScheme (HttpListenerContext context)
+ {
+ if (AuthenticationSchemeSelectorDelegate != null)
+ return AuthenticationSchemeSelectorDelegate (context.Request);
+ else
+ return auth_schemes;
+ }
+
+ internal void UnregisterContext (HttpListenerContext context)
+ {
+ lock (((ICollection)registry).SyncRoot)
+ registry.Remove (context);
+ lock (((ICollection)ctx_queue).SyncRoot) {
+ int idx = ctx_queue.IndexOf (context);
+ if (idx >= 0)
+ ctx_queue.RemoveAt (idx);
+ }
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public void Abort ()
+ {
+ if (disposed)
+ return;
+
+ if (!listening) {
+ return;
+ }
+
+ Close (true);
+ }
+
+ public IAsyncResult BeginGetContext (AsyncCallback callback, Object state)
+ {
+ CheckDisposed ();
+ if (!listening)
+ throw new InvalidOperationException ("Please, call Start before using this method.");
+
+ ListenerAsyncResult ares = new ListenerAsyncResult (callback, state);
+
+ // lock wait_queue early to avoid race conditions
+ lock (((ICollection)wait_queue).SyncRoot) {
+ lock (((ICollection)ctx_queue).SyncRoot) {
+ HttpListenerContext ctx = GetContextFromQueue ();
+ if (ctx != null) {
+ ares.Complete (ctx, true);
+ return ares;
+ }
+ }
+
+ wait_queue.Add (ares);
+ }
+
+ return ares;
+ }
+
+ public void Close ()
+ {
+ if (disposed)
+ return;
+
+ if (!listening) {
+ disposed = true;
+ return;
+ }
+
+ Close (true);
+ disposed = true;
+ }
+
+ public HttpListenerContext EndGetContext (IAsyncResult asyncResult)
+ {
+ CheckDisposed ();
+ if (asyncResult == null)
+ throw new ArgumentNullException ("asyncResult");
+
+ ListenerAsyncResult ares = asyncResult as ListenerAsyncResult;
+ if (ares == null)
+ throw new ArgumentException ("Wrong IAsyncResult.", "asyncResult");
+ if (ares.EndCalled)
+ throw new ArgumentException ("Cannot reuse this IAsyncResult");
+ ares.EndCalled = true;
+
+ if (!ares.IsCompleted)
+ ares.AsyncWaitHandle.WaitOne ();
+
+ lock (((ICollection)wait_queue).SyncRoot) {
+ int idx = wait_queue.IndexOf (ares);
+ if (idx >= 0)
+ wait_queue.RemoveAt (idx);
+ }
+
+ HttpListenerContext context = ares.GetContext ();
+ context.ParseAuthentication (SelectAuthenticationScheme (context));
+ return context; // This will throw on error.
+ }
+
+ public HttpListenerContext GetContext ()
+ {
+ // The prefixes are not checked when using the async interface!?
+ if (prefixes.Count == 0)
+ throw new InvalidOperationException ("Please, call AddPrefix before using this method.");
+
+ ListenerAsyncResult ares = (ListenerAsyncResult) BeginGetContext (null, null);
+ ares.InGet = true;
+ return EndGetContext (ares);
+ }
+
+ public void Start ()
+ {
+ CheckDisposed ();
+ if (listening)
+ return;
+
+ EndPointManager.AddListener (this);
+ listening = true;
+ }
+
+ public void Stop ()
+ {
+ CheckDisposed ();
+ listening = false;
+ Close (false);
+ }
+
+ #endregion
+ }
+}
diff --git a/websocket-sharp/Net/HttpListenerContext.cs b/websocket-sharp/Net/HttpListenerContext.cs
new file mode 100644
index 00000000..f8258323
--- /dev/null
+++ b/websocket-sharp/Net/HttpListenerContext.cs
@@ -0,0 +1,179 @@
+//
+// HttpListenerContext.cs
+// Copied from System.Net.HttpListenerContext
+//
+// Author:
+// Gonzalo Paniagua Javier (gonzalo@novell.com)
+//
+// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+// Copyright (c) 2012 sta.blockhead (sta.blockhead@gmail.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Specialized;
+using System.IO;
+using System.Net;
+using System.Security.Principal;
+using System.Text;
+
+namespace WebSocketSharp.Net {
+
+ public sealed class HttpListenerContext {
+
+ #region Private Fields
+
+ HttpConnection cnc;
+ string error;
+ int err_status;
+ HttpListenerRequest request;
+ HttpListenerResponse response;
+ IPrincipal user;
+
+ #endregion
+
+ #region Internal Fields
+
+ internal HttpListener Listener;
+
+ #endregion
+
+ #region Constructor
+
+ internal HttpListenerContext (HttpConnection cnc)
+ {
+ this.cnc = cnc;
+ err_status = 400;
+ request = new HttpListenerRequest (this);
+ response = new HttpListenerResponse (this);
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal HttpConnection Connection {
+ get { return cnc; }
+ }
+
+ internal string ErrorMessage {
+ get { return error; }
+ set { error = value; }
+ }
+
+ internal int ErrorStatus {
+ get { return err_status; }
+ set { err_status = value; }
+ }
+
+ internal bool HaveError {
+ get { return (error != null); }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ public HttpListenerRequest Request {
+ get { return request; }
+ }
+
+ public HttpListenerResponse Response {
+ get { return response; }
+ }
+
+ public IPrincipal User {
+ get { return user; }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal void ParseAuthentication (AuthenticationSchemes expectedSchemes)
+ {
+ if (expectedSchemes == AuthenticationSchemes.Anonymous)
+ return;
+
+ // TODO: Handle NTLM/Digest modes
+ string header = request.Headers ["Authorization"];
+ if (header == null || header.Length < 2)
+ return;
+
+ string [] authenticationData = header.Split (new char [] {' '}, 2);
+ if (string.Compare (authenticationData [0], "basic", true) == 0) {
+ user = ParseBasicAuthentication (authenticationData [1]);
+ }
+ // TODO: throw if malformed -> 400 bad request
+ }
+
+ internal IPrincipal ParseBasicAuthentication (string authData)
+ {
+ try {
+ // Basic AUTH Data is a formatted Base64 String
+ //string domain = null;
+ string user = null;
+ string password = null;
+ int pos = -1;
+ string authString = Encoding.Default.GetString (Convert.FromBase64String (authData));
+
+ // The format is DOMAIN\username:password
+ // Domain is optional
+
+ pos = authString.IndexOf (':');
+
+ // parse the password off the end
+ password = authString.Substring (pos+1);
+
+ // discard the password
+ authString = authString.Substring (0, pos);
+
+ // check if there is a domain
+ pos = authString.IndexOf ('\\');
+
+ if (pos > 0) {
+ //domain = authString.Substring (0, pos);
+ user = authString.Substring (pos);
+ } else {
+ user = authString;
+ }
+
+ HttpListenerBasicIdentity identity = new HttpListenerBasicIdentity (user, password);
+ // TODO: What are the roles MS sets
+ return new GenericPrincipal (identity, new string [0]);
+ } catch (Exception) {
+ // Invalid auth data is swallowed silently
+ return null;
+ }
+ }
+
+ #endregion
+
+ #region Public Method
+
+ public HttpListenerWebSocketContext AcceptWebSocket ()
+ {
+ return new HttpListenerWebSocketContext (this);
+ }
+
+ #endregion
+ }
+}
diff --git a/websocket-sharp/Net/HttpListenerException.cs b/websocket-sharp/Net/HttpListenerException.cs
new file mode 100644
index 00000000..b8842808
--- /dev/null
+++ b/websocket-sharp/Net/HttpListenerException.cs
@@ -0,0 +1,59 @@
+//
+// HttpListenerException.cs
+// Copied from System.Net.HttpListenerException
+//
+// Author:
+// Gonzalo Paniagua Javier (gonzalo@novell.com)
+//
+// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.ComponentModel;
+using System.Runtime.Serialization;
+
+namespace WebSocketSharp.Net {
+
+ [Serializable]
+ public class HttpListenerException : Win32Exception
+ {
+ public HttpListenerException ()
+ {
+ }
+
+ public HttpListenerException (int errorCode) : base (errorCode)
+ {
+ }
+
+ public HttpListenerException (int errorCode, string message) : base (errorCode, message)
+ {
+ }
+
+ protected HttpListenerException (SerializationInfo serializationInfo, StreamingContext streamingContext) : base (serializationInfo, streamingContext)
+ {
+ }
+
+ public override int ErrorCode {
+ get { return base.ErrorCode; }
+ }
+ }
+}
diff --git a/websocket-sharp/Net/HttpListenerPrefixCollection.cs b/websocket-sharp/Net/HttpListenerPrefixCollection.cs
new file mode 100644
index 00000000..904c8f46
--- /dev/null
+++ b/websocket-sharp/Net/HttpListenerPrefixCollection.cs
@@ -0,0 +1,127 @@
+//
+// HttpListenerPrefixCollection.cs
+// Copied from System.Net.HttpListenerPrefixCollection
+//
+// Author:
+// Gonzalo Paniagua Javier (gonzalo@novell.com)
+//
+// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+// Copyright (c) 2012 sta.blockhead (sta.blockhead@gmail.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Net;
+
+namespace WebSocketSharp.Net {
+
+ public class HttpListenerPrefixCollection : ICollection, IEnumerable, IEnumerable
+ {
+ HttpListener listener;
+ List prefixes;
+
+ private HttpListenerPrefixCollection ()
+ {
+ prefixes = new List ();
+ }
+
+ internal HttpListenerPrefixCollection (HttpListener listener)
+ : this ()
+ {
+ this.listener = listener;
+ }
+
+ public int Count {
+ get { return prefixes.Count; }
+ }
+
+ public bool IsReadOnly {
+ get { return false; }
+ }
+
+ public bool IsSynchronized {
+ get { return false; }
+ }
+
+ public void Add (string uriPrefix)
+ {
+ listener.CheckDisposed ();
+ ListenerPrefix.CheckUri (uriPrefix);
+ if (prefixes.Contains (uriPrefix))
+ return;
+
+ prefixes.Add (uriPrefix);
+ if (listener.IsListening)
+ EndPointManager.AddPrefix (uriPrefix, listener);
+ }
+
+ public void Clear ()
+ {
+ listener.CheckDisposed ();
+ prefixes.Clear ();
+ if (listener.IsListening)
+ EndPointManager.RemoveListener (listener);
+ }
+
+ public bool Contains (string uriPrefix)
+ {
+ listener.CheckDisposed ();
+ return prefixes.Contains (uriPrefix);
+ }
+
+ public void CopyTo (string [] array, int offset)
+ {
+ listener.CheckDisposed ();
+ prefixes.CopyTo (array, offset);
+ }
+
+ public void CopyTo (Array array, int offset)
+ {
+ listener.CheckDisposed ();
+ ((ICollection) prefixes).CopyTo (array, offset);
+ }
+
+ public IEnumerator GetEnumerator ()
+ {
+ return prefixes.GetEnumerator ();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator ()
+ {
+ return prefixes.GetEnumerator ();
+ }
+
+ public bool Remove (string uriPrefix)
+ {
+ listener.CheckDisposed ();
+ if (uriPrefix == null)
+ throw new ArgumentNullException ("uriPrefix");
+
+ bool result = prefixes.Remove (uriPrefix);
+ if (result && listener.IsListening)
+ EndPointManager.RemovePrefix (uriPrefix, listener);
+
+ return result;
+ }
+ }
+}
diff --git a/websocket-sharp/Net/HttpListenerRequest.cs b/websocket-sharp/Net/HttpListenerRequest.cs
new file mode 100644
index 00000000..1b25efa2
--- /dev/null
+++ b/websocket-sharp/Net/HttpListenerRequest.cs
@@ -0,0 +1,546 @@
+//
+// HttpListenerRequest.cs
+// Copied from System.Net.HttpListenerRequest
+//
+// Author:
+// Gonzalo Paniagua Javier (gonzalo@novell.com)
+//
+// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+// Copyright (c) 2012 sta.blockhead (sta.blockhead@gmail.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Collections.Specialized;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+
+namespace WebSocketSharp.Net {
+
+ public sealed class HttpListenerRequest {
+
+ #region Private Static Fields
+
+ static char [] separators = new char [] { ' ' };
+ static byte [] _100continue = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n");
+
+ #endregion
+
+ #region Private Fields
+
+ string [] accept_types;
+// int client_cert_error;
+ bool cl_set;
+ Encoding content_encoding;
+ long content_length;
+ HttpListenerContext context;
+ CookieCollection cookies;
+ WebHeaderCollection headers;
+ Stream input_stream;
+ bool is_chunked;
+ bool ka_set;
+ bool keep_alive;
+ string method;
+// bool no_get_certificate;
+ Version version;
+ NameValueCollection query_string; // check if null is ok, check if read-only, check case-sensitiveness
+ string raw_url;
+ Uri referrer;
+ Uri url;
+ string [] user_languages;
+
+ #endregion
+
+ #region Constructor
+
+ internal HttpListenerRequest (HttpListenerContext context)
+ {
+ this.context = context;
+ headers = new WebHeaderCollection ();
+ version = HttpVersion.Version10;
+ }
+
+ #endregion
+
+ #region Properties
+
+ public string [] AcceptTypes {
+ get { return accept_types; }
+ }
+
+ // TODO: Always returns 0
+ public int ClientCertificateError {
+ get {
+/*
+ if (no_get_certificate)
+ throw new InvalidOperationException (
+ "Call GetClientCertificate() before calling this method.");
+ return client_cert_error;
+*/
+ return 0;
+ }
+ }
+
+ public Encoding ContentEncoding {
+ get {
+ if (content_encoding == null)
+ content_encoding = Encoding.Default;
+ return content_encoding;
+ }
+ }
+
+ public long ContentLength64 {
+ get { return content_length; }
+ }
+
+ public string ContentType {
+ get { return headers ["content-type"]; }
+ }
+
+ public CookieCollection Cookies {
+ get {
+ // TODO: check if the collection is read-only
+ if (cookies == null)
+ cookies = new CookieCollection ();
+ return cookies;
+ }
+ }
+
+ public bool HasEntityBody {
+ get { return (content_length > 0 || is_chunked); }
+ }
+
+ public NameValueCollection Headers {
+ get { return headers; }
+ }
+
+ public string HttpMethod {
+ get { return method; }
+ }
+
+ public Stream InputStream {
+ get {
+ if (input_stream == null) {
+ if (is_chunked || content_length > 0)
+ input_stream = context.Connection.GetRequestStream (is_chunked, content_length);
+ else
+ input_stream = Stream.Null;
+ }
+
+ return input_stream;
+ }
+ }
+
+ // TODO: Always returns false
+ public bool IsAuthenticated {
+ get { return false; }
+ }
+
+ public bool IsLocal {
+ get { return IPAddress.IsLoopback (RemoteEndPoint.Address); }
+ }
+
+ public bool IsSecureConnection {
+ get { return context.Connection.IsSecure; }
+ }
+
+ public bool IsWebSocketRequest {
+ get {
+ if (method != "GET")
+ return false;
+
+ if (version != HttpVersion.Version11)
+ return false;
+
+ if (!headers.Exists("Upgrade", "websocket"))
+ return false;
+
+ if (!headers.Exists("Connection", "Upgrade"))
+ return false;
+
+ if (!headers.Exists("Host"))
+ return false;
+
+ if (!headers.Exists("Sec-WebSocket-Key"))
+ return false;
+
+ if (!headers.Exists("Sec-WebSocket-Version"))
+ return false;
+
+ return true;
+ }
+ }
+
+ public bool KeepAlive {
+ get {
+ if (ka_set)
+ return keep_alive;
+
+ ka_set = true;
+ // 1. Connection header
+ // 2. Protocol (1.1 == keep-alive by default)
+ // 3. Keep-Alive header
+ string cnc = headers ["Connection"];
+ if (!String.IsNullOrEmpty (cnc)) {
+ keep_alive = (0 == String.Compare (cnc, "keep-alive", StringComparison.OrdinalIgnoreCase));
+ } else if (version == HttpVersion.Version11) {
+ keep_alive = true;
+ } else {
+ cnc = headers ["keep-alive"];
+ if (!String.IsNullOrEmpty (cnc))
+ keep_alive = (0 != String.Compare (cnc, "closed", StringComparison.OrdinalIgnoreCase));
+ }
+ return keep_alive;
+ }
+ }
+
+ public IPEndPoint LocalEndPoint {
+ get { return context.Connection.LocalEndPoint; }
+ }
+
+ public Version ProtocolVersion {
+ get { return version; }
+ }
+
+ public NameValueCollection QueryString {
+ get { return query_string; }
+ }
+
+ public string RawUrl {
+ get { return raw_url; }
+ }
+
+ public IPEndPoint RemoteEndPoint {
+ get { return context.Connection.RemoteEndPoint; }
+ }
+
+ // TODO: Always returns Guid.Empty
+ public Guid RequestTraceIdentifier {
+ get { return Guid.Empty; }
+ }
+
+ public Uri Url {
+ get { return url; }
+ }
+
+ public Uri UrlReferrer {
+ get { return referrer; }
+ }
+
+ public string UserAgent {
+ get { return headers ["user-agent"]; }
+ }
+
+ public string UserHostAddress {
+ get { return LocalEndPoint.ToString (); }
+ }
+
+ public string UserHostName {
+ get { return headers ["host"]; }
+ }
+
+ public string [] UserLanguages {
+ get { return user_languages; }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ void CreateQueryString (string query)
+ {
+ if (query == null || query.Length == 0) {
+ query_string = new NameValueCollection (1);
+ return;
+ }
+
+ query_string = new NameValueCollection ();
+ if (query [0] == '?')
+ query = query.Substring (1);
+ string [] components = query.Split ('&');
+ foreach (string kv in components) {
+ int pos = kv.IndexOf ('=');
+ if (pos == -1) {
+ query_string.Add (null, HttpUtility.UrlDecode (kv));
+ } else {
+ string key = HttpUtility.UrlDecode (kv.Substring (0, pos));
+ string val = HttpUtility.UrlDecode (kv.Substring (pos + 1));
+
+ query_string.Add (key, val);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal void AddHeader (string header)
+ {
+ int colon = header.IndexOf (':');
+ if (colon == -1 || colon == 0) {
+ context.ErrorMessage = "Bad Request";
+ context.ErrorStatus = 400;
+ return;
+ }
+
+ string name = header.Substring (0, colon).Trim ();
+ string val = header.Substring (colon + 1).Trim ();
+ string lower = name.ToLower (CultureInfo.InvariantCulture);
+ headers.SetInternal (name, val);
+ switch (lower) {
+ case "accept-language":
+ user_languages = val.Split (','); // yes, only split with a ','
+ break;
+ case "accept":
+ accept_types = val.Split (','); // yes, only split with a ','
+ break;
+ case "content-length":
+ try {
+ //TODO: max. content_length?
+ content_length = Int64.Parse (val.Trim ());
+ if (content_length < 0)
+ context.ErrorMessage = "Invalid Content-Length.";
+ cl_set = true;
+ } catch {
+ context.ErrorMessage = "Invalid Content-Length.";
+ }
+
+ break;
+ case "referer":
+ try {
+ referrer = new Uri (val);
+ } catch {
+ referrer = new Uri ("http://someone.is.screwing.with.the.headers.com/");
+ }
+ break;
+ case "cookie":
+ if (cookies == null)
+ cookies = new CookieCollection();
+
+ string[] cookieStrings = val.Split(new char[] {',', ';'});
+ Cookie current = null;
+ int version = 0;
+ foreach (string cookieString in cookieStrings) {
+ string str = cookieString.Trim ();
+ if (str.Length == 0)
+ continue;
+ if (str.StartsWith ("$Version")) {
+ version = Int32.Parse (Unquote (str.Substring (str.IndexOf ('=') + 1)));
+ } else if (str.StartsWith ("$Path")) {
+ if (current != null)
+ current.Path = str.Substring (str.IndexOf ('=') + 1).Trim ();
+ } else if (str.StartsWith ("$Domain")) {
+ if (current != null)
+ current.Domain = str.Substring (str.IndexOf ('=') + 1).Trim ();
+ } else if (str.StartsWith ("$Port")) {
+ if (current != null)
+ current.Port = str.Substring (str.IndexOf ('=') + 1).Trim ();
+ } else {
+ if (current != null) {
+ cookies.Add (current);
+ }
+ current = new Cookie ();
+ int idx = str.IndexOf ('=');
+ if (idx > 0) {
+ current.Name = str.Substring (0, idx).Trim ();
+ current.Value = str.Substring (idx + 1).Trim ();
+ } else {
+ current.Name = str.Trim ();
+ current.Value = String.Empty;
+ }
+ current.Version = version;
+ }
+ }
+ if (current != null) {
+ cookies.Add (current);
+ }
+ break;
+ }
+ }
+
+ internal void FinishInitialization ()
+ {
+ string host = UserHostName;
+ if (version > HttpVersion.Version10 && (host == null || host.Length == 0)) {
+ context.ErrorMessage = "Invalid host name";
+ return;
+ }
+
+ string path;
+ Uri raw_uri = null;
+ if (raw_url.MaybeUri () && Uri.TryCreate (raw_url, UriKind.Absolute, out raw_uri))
+ path = raw_uri.PathAndQuery;
+ else
+ path = HttpUtility.UrlDecode (raw_url);
+
+ if ((host == null || host.Length == 0))
+ host = UserHostAddress;
+
+ if (raw_uri != null)
+ host = raw_uri.Host;
+
+ int colon = host.IndexOf (':');
+ if (colon >= 0)
+ host = host.Substring (0, colon);
+
+ string base_uri = String.Format ("{0}://{1}:{2}",
+ (IsSecureConnection) ? "https" : "http",
+ host,
+ LocalEndPoint.Port);
+
+ if (!Uri.TryCreate (base_uri + path, UriKind.Absolute, out url)){
+ context.ErrorMessage = "Invalid url: " + base_uri + path;
+ return;
+ }
+
+ CreateQueryString (url.Query);
+
+ if (version >= HttpVersion.Version11) {
+ string t_encoding = Headers ["Transfer-Encoding"];
+ is_chunked = (t_encoding != null && String.Compare (t_encoding, "chunked", StringComparison.OrdinalIgnoreCase) == 0);
+ // 'identity' is not valid!
+ if (t_encoding != null && !is_chunked) {
+ context.Connection.SendError (null, 501);
+ return;
+ }
+ }
+
+ if (!is_chunked && !cl_set) {
+ if (String.Compare (method, "POST", StringComparison.OrdinalIgnoreCase) == 0 ||
+ String.Compare (method, "PUT", StringComparison.OrdinalIgnoreCase) == 0) {
+ context.Connection.SendError (null, 411);
+ return;
+ }
+ }
+
+ if (String.Compare (Headers ["Expect"], "100-continue", StringComparison.OrdinalIgnoreCase) == 0) {
+ ResponseStream output = context.Connection.GetResponseStream ();
+ output.InternalWrite (_100continue, 0, _100continue.Length);
+ }
+ }
+
+ // returns true is the stream could be reused.
+ internal bool FlushInput ()
+ {
+ if (!HasEntityBody)
+ return true;
+
+ int length = 2048;
+ if (content_length > 0)
+ length = (int) Math.Min (content_length, (long) length);
+
+ byte [] bytes = new byte [length];
+ while (true) {
+ // TODO: test if MS has a timeout when doing this
+ try {
+ IAsyncResult ares = InputStream.BeginRead (bytes, 0, length, null, null);
+ if (!ares.IsCompleted && !ares.AsyncWaitHandle.WaitOne (100))
+ return false;
+ if (InputStream.EndRead (ares) <= 0)
+ return true;
+ } catch {
+ return false;
+ }
+ }
+ }
+
+ internal void SetRequestLine (string req)
+ {
+ string [] parts = req.Split (separators, 3);
+ if (parts.Length != 3) {
+ context.ErrorMessage = "Invalid request line (parts).";
+ return;
+ }
+
+ method = parts [0];
+ foreach (char c in method){
+ int ic = (int) c;
+
+ if ((ic >= 'A' && ic <= 'Z') ||
+ (ic > 32 && c < 127 && c != '(' && c != ')' && c != '<' &&
+ c != '<' && c != '>' && c != '@' && c != ',' && c != ';' &&
+ c != ':' && c != '\\' && c != '"' && c != '/' && c != '[' &&
+ c != ']' && c != '?' && c != '=' && c != '{' && c != '}'))
+ continue;
+
+ context.ErrorMessage = "(Invalid verb)";
+ return;
+ }
+
+ raw_url = parts [1];
+ if (parts [2].Length != 8 || !parts [2].StartsWith ("HTTP/")) {
+ context.ErrorMessage = "Invalid request line (version).";
+ return;
+ }
+
+ try {
+ version = new Version (parts [2].Substring (5));
+ if (version.Major < 1)
+ throw new Exception ();
+ } catch {
+ context.ErrorMessage = "Invalid request line (version).";
+ return;
+ }
+ }
+
+ internal static string Unquote (String str) {
+ int start = str.IndexOf ('\"');
+ int end = str.LastIndexOf ('\"');
+ if (start >= 0 && end >=0)
+ str = str.Substring (start + 1, end - 1);
+ return str.Trim ();
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ // TODO: Always returns null
+ public IAsyncResult BeginGetClientCertificate (AsyncCallback requestCallback, Object state)
+ {
+ return null;
+ }
+
+ // TODO: Always returns null
+ public X509Certificate2 EndGetClientCertificate (IAsyncResult asyncResult)
+ {
+ // set no_client_certificate once done.
+
+ return null;
+ }
+
+ // TODO: Always returns null
+ public X509Certificate2 GetClientCertificate ()
+ {
+ // set no_client_certificate once done.
+
+ // InvalidOp if call in progress.
+ return null;
+ }
+
+ #endregion
+ }
+}
diff --git a/websocket-sharp/Net/HttpListenerResponse.cs b/websocket-sharp/Net/HttpListenerResponse.cs
new file mode 100644
index 00000000..4ae5344d
--- /dev/null
+++ b/websocket-sharp/Net/HttpListenerResponse.cs
@@ -0,0 +1,510 @@
+//
+// HttpListenerResponse.cs
+// Copied from System.Net.HttpListenerResponse
+//
+// Author:
+// Gonzalo Paniagua Javier (gonzalo@novell.com)
+//
+// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Text;
+
+namespace WebSocketSharp.Net {
+
+ public sealed class HttpListenerResponse : IDisposable
+ {
+ bool disposed;
+ Encoding content_encoding;
+ long content_length;
+ bool cl_set;
+ string content_type;
+ CookieCollection cookies;
+ WebHeaderCollection headers = new WebHeaderCollection ();
+ bool keep_alive = true;
+ ResponseStream output_stream;
+ Version version = HttpVersion.Version11;
+ string location;
+ int status_code = 200;
+ string status_description = "OK";
+ bool chunked;
+ HttpListenerContext context;
+ internal bool HeadersSent;
+ bool force_close_chunked;
+
+ internal HttpListenerResponse (HttpListenerContext context)
+ {
+ this.context = context;
+ }
+
+ internal bool ForceCloseChunked {
+ get { return force_close_chunked; }
+ }
+
+ public Encoding ContentEncoding {
+ get {
+ if (content_encoding == null)
+ content_encoding = Encoding.Default;
+ return content_encoding;
+ }
+ set {
+ if (disposed)
+ throw new ObjectDisposedException (GetType ().ToString ());
+
+ //TODO: is null ok?
+ if (HeadersSent)
+ throw new InvalidOperationException ("Cannot be changed after headers are sent.");
+
+ content_encoding = value;
+ }
+ }
+
+ public long ContentLength64 {
+ get { return content_length; }
+ set {
+ if (disposed)
+ throw new ObjectDisposedException (GetType ().ToString ());
+
+ if (HeadersSent)
+ throw new InvalidOperationException ("Cannot be changed after headers are sent.");
+
+ if (value < 0)
+ throw new ArgumentOutOfRangeException ("Must be >= 0", "value");
+
+ cl_set = true;
+ content_length = value;
+ }
+ }
+
+ public string ContentType {
+ get { return content_type; }
+ set {
+ // TODO: is null ok?
+ if (disposed)
+ throw new ObjectDisposedException (GetType ().ToString ());
+
+ if (HeadersSent)
+ throw new InvalidOperationException ("Cannot be changed after headers are sent.");
+
+ content_type = value;
+ }
+ }
+
+ // RFC 2109, 2965 + the netscape specification at http://wp.netscape.com/newsref/std/cookie_spec.html
+ public CookieCollection Cookies {
+ get {
+ if (cookies == null)
+ cookies = new CookieCollection ();
+ return cookies;
+ }
+ set { cookies = value; } // null allowed?
+ }
+
+ public WebHeaderCollection Headers {
+ get { return headers; }
+ set {
+ /**
+ * "If you attempt to set a Content-Length, Keep-Alive, Transfer-Encoding, or
+ * WWW-Authenticate header using the Headers property, an exception will be
+ * thrown. Use the KeepAlive or ContentLength64 properties to set these headers.
+ * You cannot set the Transfer-Encoding or WWW-Authenticate headers manually."
+ */
+ // TODO: check if this is marked readonly after headers are sent.
+ headers = value;
+ }
+ }
+
+ public bool KeepAlive {
+ get { return keep_alive; }
+ set {
+ if (disposed)
+ throw new ObjectDisposedException (GetType ().ToString ());
+
+ if (HeadersSent)
+ throw new InvalidOperationException ("Cannot be changed after headers are sent.");
+
+ keep_alive = value;
+ }
+ }
+
+ public Stream OutputStream {
+ get {
+ if (output_stream == null)
+ output_stream = context.Connection.GetResponseStream ();
+ return output_stream;
+ }
+ }
+
+ public Version ProtocolVersion {
+ get { return version; }
+ set {
+ if (disposed)
+ throw new ObjectDisposedException (GetType ().ToString ());
+
+ if (HeadersSent)
+ throw new InvalidOperationException ("Cannot be changed after headers are sent.");
+
+ if (value == null)
+ throw new ArgumentNullException ("value");
+
+ if (value.Major != 1 || (value.Minor != 0 && value.Minor != 1))
+ throw new ArgumentException ("Must be 1.0 or 1.1", "value");
+
+ if (disposed)
+ throw new ObjectDisposedException (GetType ().ToString ());
+
+ version = value;
+ }
+ }
+
+ public string RedirectLocation {
+ get { return location; }
+ set {
+ if (disposed)
+ throw new ObjectDisposedException (GetType ().ToString ());
+
+ if (HeadersSent)
+ throw new InvalidOperationException ("Cannot be changed after headers are sent.");
+
+ location = value;
+ }
+ }
+
+ public bool SendChunked {
+ get { return chunked; }
+ set {
+ if (disposed)
+ throw new ObjectDisposedException (GetType ().ToString ());
+
+ if (HeadersSent)
+ throw new InvalidOperationException ("Cannot be changed after headers are sent.");
+
+ chunked = value;
+ }
+ }
+
+ public int StatusCode {
+ get { return status_code; }
+ set {
+ if (disposed)
+ throw new ObjectDisposedException (GetType ().ToString ());
+
+ if (HeadersSent)
+ throw new InvalidOperationException ("Cannot be changed after headers are sent.");
+
+ if (value < 100 || value > 999)
+ throw new ProtocolViolationException ("StatusCode must be between 100 and 999.");
+ status_code = value;
+ status_description = GetStatusDescription (value);
+ }
+ }
+
+ internal static string GetStatusDescription (int code)
+ {
+ switch (code){
+ case 100: return "Continue";
+ case 101: return "Switching Protocols";
+ case 102: return "Processing";
+ case 200: return "OK";
+ case 201: return "Created";
+ case 202: return "Accepted";
+ case 203: return "Non-Authoritative Information";
+ case 204: return "No Content";
+ case 205: return "Reset Content";
+ case 206: return "Partial Content";
+ case 207: return "Multi-Status";
+ case 300: return "Multiple Choices";
+ case 301: return "Moved Permanently";
+ case 302: return "Found";
+ case 303: return "See Other";
+ case 304: return "Not Modified";
+ case 305: return "Use Proxy";
+ case 307: return "Temporary Redirect";
+ case 400: return "Bad Request";
+ case 401: return "Unauthorized";
+ case 402: return "Payment Required";
+ case 403: return "Forbidden";
+ case 404: return "Not Found";
+ case 405: return "Method Not Allowed";
+ case 406: return "Not Acceptable";
+ case 407: return "Proxy Authentication Required";
+ case 408: return "Request Timeout";
+ case 409: return "Conflict";
+ case 410: return "Gone";
+ case 411: return "Length Required";
+ case 412: return "Precondition Failed";
+ case 413: return "Request Entity Too Large";
+ case 414: return "Request-Uri Too Long";
+ case 415: return "Unsupported Media Type";
+ case 416: return "Requested Range Not Satisfiable";
+ case 417: return "Expectation Failed";
+ case 422: return "Unprocessable Entity";
+ case 423: return "Locked";
+ case 424: return "Failed Dependency";
+ case 500: return "Internal Server Error";
+ case 501: return "Not Implemented";
+ case 502: return "Bad Gateway";
+ case 503: return "Service Unavailable";
+ case 504: return "Gateway Timeout";
+ case 505: return "Http Version Not Supported";
+ case 507: return "Insufficient Storage";
+ }
+ return "";
+ }
+
+ public string StatusDescription {
+ get { return status_description; }
+ set {
+ status_description = value;
+ }
+ }
+
+ void IDisposable.Dispose ()
+ {
+ Close (true); //TODO: Abort or Close?
+ }
+
+ public void Abort ()
+ {
+ if (disposed)
+ return;
+
+ Close (true);
+ }
+
+ public void AddHeader (string name, string value)
+ {
+ if (name == null)
+ throw new ArgumentNullException ("name");
+
+ if (name == "")
+ throw new ArgumentException ("'name' cannot be empty", "name");
+
+ //TODO: check for forbidden headers and invalid characters
+ if (value.Length > 65535)
+ throw new ArgumentOutOfRangeException ("value");
+
+ headers.Set (name, value);
+ }
+
+ public void AppendCookie (Cookie cookie)
+ {
+ if (cookie == null)
+ throw new ArgumentNullException ("cookie");
+
+ Cookies.Add (cookie);
+ }
+
+ public void AppendHeader (string name, string value)
+ {
+ if (name == null)
+ throw new ArgumentNullException ("name");
+
+ if (name == "")
+ throw new ArgumentException ("'name' cannot be empty", "name");
+
+ if (value.Length > 65535)
+ throw new ArgumentOutOfRangeException ("value");
+
+ headers.Add (name, value);
+ }
+
+ void Close (bool force)
+ {
+ disposed = true;
+ context.Connection.Close (force);
+ }
+
+ public void Close ()
+ {
+ if (disposed)
+ return;
+
+ Close (false);
+ }
+
+ public void Close (byte [] responseEntity, bool willBlock)
+ {
+ if (disposed)
+ return;
+
+ if (responseEntity == null)
+ throw new ArgumentNullException ("responseEntity");
+
+ //TODO: if willBlock -> BeginWrite + Close ?
+ ContentLength64 = responseEntity.Length;
+ OutputStream.Write (responseEntity, 0, (int) content_length);
+ Close (false);
+ }
+
+ public void CopyFrom (HttpListenerResponse templateResponse)
+ {
+ headers.Clear ();
+ headers.Add (templateResponse.headers);
+ content_length = templateResponse.content_length;
+ status_code = templateResponse.status_code;
+ status_description = templateResponse.status_description;
+ keep_alive = templateResponse.keep_alive;
+ version = templateResponse.version;
+ }
+
+ public void Redirect (string url)
+ {
+ StatusCode = 302; // Found
+ location = url;
+ }
+
+ bool FindCookie (Cookie cookie)
+ {
+ string name = cookie.Name;
+ string domain = cookie.Domain;
+ string path = cookie.Path;
+ foreach (Cookie c in cookies) {
+ if (name != c.Name)
+ continue;
+ if (domain != c.Domain)
+ continue;
+ if (path == c.Path)
+ return true;
+ }
+
+ return false;
+ }
+
+ internal void SendHeaders (bool closing, MemoryStream ms)
+ {
+ Encoding encoding = content_encoding;
+ if (encoding == null)
+ encoding = Encoding.Default;
+
+ if (content_type != null) {
+ if (content_encoding != null && content_type.IndexOf ("charset=", StringComparison.Ordinal) == -1) {
+ string enc_name = content_encoding.WebName;
+ headers.SetInternal ("Content-Type", content_type + "; charset=" + enc_name);
+ } else {
+ headers.SetInternal ("Content-Type", content_type);
+ }
+ }
+
+ if (headers ["Server"] == null)
+ headers.SetInternal ("Server", "Mono-HTTPAPI/1.0");
+
+ CultureInfo inv = CultureInfo.InvariantCulture;
+ if (headers ["Date"] == null)
+ headers.SetInternal ("Date", DateTime.UtcNow.ToString ("r", inv));
+
+ if (!chunked) {
+ if (!cl_set && closing) {
+ cl_set = true;
+ content_length = 0;
+ }
+
+ if (cl_set)
+ headers.SetInternal ("Content-Length", content_length.ToString (inv));
+ }
+
+ Version v = context.Request.ProtocolVersion;
+ if (!cl_set && !chunked && v >= HttpVersion.Version11)
+ chunked = true;
+
+ /* Apache forces closing the connection for these status codes:
+ * HttpStatusCode.BadRequest 400
+ * HttpStatusCode.RequestTimeout 408
+ * HttpStatusCode.LengthRequired 411
+ * HttpStatusCode.RequestEntityTooLarge 413
+ * HttpStatusCode.RequestUriTooLong 414
+ * HttpStatusCode.InternalServerError 500
+ * HttpStatusCode.ServiceUnavailable 503
+ */
+ bool conn_close = (status_code == 400 || status_code == 408 || status_code == 411 ||
+ status_code == 413 || status_code == 414 || status_code == 500 ||
+ status_code == 503);
+
+ if (conn_close == false)
+ conn_close = !context.Request.KeepAlive;
+
+ // They sent both KeepAlive: true and Connection: close!?
+ if (!keep_alive || conn_close) {
+ headers.SetInternal ("Connection", "close");
+ conn_close = true;
+ }
+
+ if (chunked)
+ headers.SetInternal ("Transfer-Encoding", "chunked");
+
+ int reuses = context.Connection.Reuses;
+ if (reuses >= 100) {
+ force_close_chunked = true;
+ if (!conn_close) {
+ headers.SetInternal ("Connection", "close");
+ conn_close = true;
+ }
+ }
+
+ if (!conn_close) {
+ headers.SetInternal ("Keep-Alive", String.Format ("timeout=15,max={0}", 100 - reuses));
+ if (context.Request.ProtocolVersion <= HttpVersion.Version10)
+ headers.SetInternal ("Connection", "keep-alive");
+ }
+
+ if (location != null)
+ headers.SetInternal ("Location", location);
+
+ if (cookies != null) {
+ foreach (Cookie cookie in cookies)
+ headers.SetInternal ("Set-Cookie", cookie.ToClientString ());
+ }
+
+ StreamWriter writer = new StreamWriter (ms, encoding, 256);
+ writer.Write ("HTTP/{0} {1} {2}\r\n", version, status_code, status_description);
+ string headers_str = headers.ToStringMultiValue ();
+ writer.Write (headers_str);
+ writer.Flush ();
+ int preamble = (encoding.CodePage == 65001) ? 3 : encoding.GetPreamble ().Length;
+ if (output_stream == null)
+ output_stream = context.Connection.GetResponseStream ();
+
+ /* Assumes that the ms was at position 0 */
+ ms.Position = preamble;
+ HeadersSent = true;
+ }
+
+ public void SetCookie (Cookie cookie)
+ {
+ if (cookie == null)
+ throw new ArgumentNullException ("cookie");
+
+ if (cookies != null) {
+ if (FindCookie (cookie))
+ throw new ArgumentException ("The cookie already exists.");
+ } else {
+ cookies = new CookieCollection ();
+ }
+
+ cookies.Add (cookie);
+ }
+ }
+}
diff --git a/websocket-sharp/Net/HttpListenerWebSocketContext.cs b/websocket-sharp/Net/HttpListenerWebSocketContext.cs
new file mode 100644
index 00000000..e0d24925
--- /dev/null
+++ b/websocket-sharp/Net/HttpListenerWebSocketContext.cs
@@ -0,0 +1,101 @@
+#region MIT License
+/**
+ * HttpListenerWebSocketContext.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012 sta.blockhead
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Security.Principal;
+
+namespace WebSocketSharp.Net {
+
+ public class HttpListenerWebSocketContext : WebSocketContext
+ {
+ private HttpListenerContext _context;
+ private WebSocket _socket;
+
+ internal HttpListenerWebSocketContext(HttpListenerContext context)
+ {
+ _context = context;
+ _socket = new WebSocket(this);
+ }
+
+ internal HttpListenerContext BaseContext {
+ get { return _context; }
+ }
+
+ public override CookieCollection CookieCollection {
+ get { return _context.Request.Cookies; }
+ }
+
+ public override NameValueCollection Headers {
+ get { return _context.Request.Headers; }
+ }
+
+ public override bool IsAuthenticated {
+ get { return _context.Request.IsAuthenticated; }
+ }
+
+ public override bool IsSecureConnection {
+ get { return _context.Request.IsSecureConnection; }
+ }
+
+ public override bool IsLocal {
+ get { return _context.Request.IsLocal; }
+ }
+
+ public override string Origin {
+ get { return Headers["Origin"]; }
+ }
+
+ public override Uri RequestUri {
+ get { return _context.Request.RawUrl.ToUri(); }
+ }
+
+ public override string SecWebSocketKey {
+ get { return Headers["Sec-WebSocket-Key"]; }
+ }
+
+ public override IEnumerable SecWebSocketProtocols {
+ get { return Headers.GetValues("Sec-WebSocket-Protocol"); }
+ }
+
+ public override string SecWebSocketVersion {
+ get { return Headers["Sec-WebSocket-Version"]; }
+ }
+
+ public override IPrincipal User {
+ get { return _context.User; }
+ }
+
+ public override WebSocket WebSocket {
+ get { return _socket; }
+ }
+ }
+}
diff --git a/websocket-sharp/Net/HttpStatusCode.cs b/websocket-sharp/Net/HttpStatusCode.cs
new file mode 100644
index 00000000..0338ab01
--- /dev/null
+++ b/websocket-sharp/Net/HttpStatusCode.cs
@@ -0,0 +1,84 @@
+//
+// HttpStatusCode.cs
+// Copied from System.Net.HttpStatusCode
+//
+// This code was automatically generated from
+// ECMA CLI XML Library Specification.
+// Generator: libgen.xsl [1.0; (C) Sergey Chaban (serge@wildwestsoftware.com)]
+// Created: Wed, 5 Sep 2001 06:32:05 UTC
+// Source file: AllTypes.xml
+// URL: http://msdn.microsoft.com/net/ecma/AllTypes.xml
+//
+// Copyright (C) 2001 Ximian, Inc. (http://www.ximian.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+namespace WebSocketSharp.Net {
+
+ public enum HttpStatusCode {
+ Continue = 100,
+ SwitchingProtocols = 101,
+ OK = 200,
+ Created = 201,
+ Accepted = 202,
+ NonAuthoritativeInformation = 203,
+ NoContent = 204,
+ ResetContent = 205,
+ PartialContent = 206,
+ MultipleChoices = 300,
+ Ambiguous = 300,
+ MovedPermanently = 301,
+ Moved = 301,
+ Found = 302,
+ Redirect = 302,
+ SeeOther = 303,
+ RedirectMethod = 303,
+ NotModified = 304,
+ UseProxy = 305,
+ Unused = 306,
+ TemporaryRedirect = 307,
+ RedirectKeepVerb = 307,
+ BadRequest = 400,
+ Unauthorized = 401,
+ PaymentRequired = 402,
+ Forbidden = 403,
+ NotFound = 404,
+ MethodNotAllowed = 405,
+ NotAcceptable = 406,
+ ProxyAuthenticationRequired = 407,
+ RequestTimeout = 408,
+ Conflict = 409,
+ Gone = 410,
+ LengthRequired = 411,
+ PreconditionFailed = 412,
+ RequestEntityTooLarge = 413,
+ RequestUriTooLong = 414,
+ UnsupportedMediaType = 415,
+ RequestedRangeNotSatisfiable = 416,
+ ExpectationFailed = 417,
+ InternalServerError = 500,
+ NotImplemented = 501,
+ BadGateway = 502,
+ ServiceUnavailable = 503,
+ GatewayTimeout = 504,
+ HttpVersionNotSupported = 505,
+ }
+}
diff --git a/websocket-sharp/Net/HttpStreamAsyncResult.cs b/websocket-sharp/Net/HttpStreamAsyncResult.cs
new file mode 100644
index 00000000..6ae5e6f0
--- /dev/null
+++ b/websocket-sharp/Net/HttpStreamAsyncResult.cs
@@ -0,0 +1,98 @@
+//
+// HttpStreamAsyncResult.cs
+// Copied from System.Net.HttpStreamAsyncResult
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo@novell.com)
+//
+// Copyright (C) 2005 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Net;
+using System.Threading;
+
+namespace WebSocketSharp.Net {
+
+ class HttpStreamAsyncResult : IAsyncResult
+ {
+ bool completed;
+ ManualResetEvent handle;
+ object locker = new object ();
+
+ internal AsyncCallback Callback;
+ internal int Count;
+ internal byte [] Buffer;
+ internal Exception Error;
+ internal int Offset;
+ internal object State;
+ internal int SynchRead;
+
+ public object AsyncState {
+ get { return State; }
+ }
+
+ public WaitHandle AsyncWaitHandle {
+ get {
+ lock (locker) {
+ if (handle == null)
+ handle = new ManualResetEvent (completed);
+ }
+
+ return handle;
+ }
+ }
+
+ public bool CompletedSynchronously {
+ get { return (SynchRead == Count); }
+ }
+
+ public bool IsCompleted {
+ get {
+ lock (locker) {
+ return completed;
+ }
+ }
+ }
+
+ public void Complete ()
+ {
+ lock (locker) {
+ if (completed)
+ return;
+
+ completed = true;
+ if (handle != null)
+ handle.Set ();
+
+ if (Callback != null)
+ Callback.BeginInvoke (this, null, null);
+ }
+ }
+
+ public void Complete (Exception e)
+ {
+ Error = e;
+ Complete ();
+ }
+ }
+}
diff --git a/websocket-sharp/Net/HttpUtility.cs b/websocket-sharp/Net/HttpUtility.cs
new file mode 100644
index 00000000..7eaffa04
--- /dev/null
+++ b/websocket-sharp/Net/HttpUtility.cs
@@ -0,0 +1,1116 @@
+//
+// HttpUtility.cs
+// Copied from System.Net.HttpUtility
+//
+// Authors:
+// Patrik Torstensson (Patrik.Torstensson@labs2.com)
+// Wictor Wilén (decode/encode functions) (wictor@ibizkit.se)
+// Tim Coleman (tim@timcoleman.com)
+// Gonzalo Paniagua Javier (gonzalo@ximian.com)
+//
+// Copyright (C) 2005-2009 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Security.Permissions;
+using System.Text;
+
+namespace WebSocketSharp.Net {
+
+ internal sealed class HttpUtility {
+
+ sealed class HttpQSCollection : NameValueCollection
+ {
+ public override string ToString ()
+ {
+ int count = Count;
+ if (count == 0)
+ return "";
+ StringBuilder sb = new StringBuilder ();
+ string [] keys = AllKeys;
+ for (int i = 0; i < count; i++) {
+ sb.AppendFormat ("{0}={1}&", keys [i], this [keys [i]]);
+ }
+ if (sb.Length > 0)
+ sb.Length--;
+ return sb.ToString ();
+ }
+ }
+ #region Fields
+
+ static Hashtable entities;
+ static object lock_ = new object ();
+
+ #endregion // Fields
+
+ static Hashtable Entities {
+ get {
+ lock (lock_) {
+ if (entities == null)
+ InitEntities ();
+
+ return entities;
+ }
+ }
+ }
+
+ #region Constructors
+
+ static void InitEntities ()
+ {
+ // Build the hash table of HTML entity references. This list comes
+ // from the HTML 4.01 W3C recommendation.
+ entities = new Hashtable ();
+ entities.Add ("nbsp", '\u00A0');
+ entities.Add ("iexcl", '\u00A1');
+ entities.Add ("cent", '\u00A2');
+ entities.Add ("pound", '\u00A3');
+ entities.Add ("curren", '\u00A4');
+ entities.Add ("yen", '\u00A5');
+ entities.Add ("brvbar", '\u00A6');
+ entities.Add ("sect", '\u00A7');
+ entities.Add ("uml", '\u00A8');
+ entities.Add ("copy", '\u00A9');
+ entities.Add ("ordf", '\u00AA');
+ entities.Add ("laquo", '\u00AB');
+ entities.Add ("not", '\u00AC');
+ entities.Add ("shy", '\u00AD');
+ entities.Add ("reg", '\u00AE');
+ entities.Add ("macr", '\u00AF');
+ entities.Add ("deg", '\u00B0');
+ entities.Add ("plusmn", '\u00B1');
+ entities.Add ("sup2", '\u00B2');
+ entities.Add ("sup3", '\u00B3');
+ entities.Add ("acute", '\u00B4');
+ entities.Add ("micro", '\u00B5');
+ entities.Add ("para", '\u00B6');
+ entities.Add ("middot", '\u00B7');
+ entities.Add ("cedil", '\u00B8');
+ entities.Add ("sup1", '\u00B9');
+ entities.Add ("ordm", '\u00BA');
+ entities.Add ("raquo", '\u00BB');
+ entities.Add ("frac14", '\u00BC');
+ entities.Add ("frac12", '\u00BD');
+ entities.Add ("frac34", '\u00BE');
+ entities.Add ("iquest", '\u00BF');
+ entities.Add ("Agrave", '\u00C0');
+ entities.Add ("Aacute", '\u00C1');
+ entities.Add ("Acirc", '\u00C2');
+ entities.Add ("Atilde", '\u00C3');
+ entities.Add ("Auml", '\u00C4');
+ entities.Add ("Aring", '\u00C5');
+ entities.Add ("AElig", '\u00C6');
+ entities.Add ("Ccedil", '\u00C7');
+ entities.Add ("Egrave", '\u00C8');
+ entities.Add ("Eacute", '\u00C9');
+ entities.Add ("Ecirc", '\u00CA');
+ entities.Add ("Euml", '\u00CB');
+ entities.Add ("Igrave", '\u00CC');
+ entities.Add ("Iacute", '\u00CD');
+ entities.Add ("Icirc", '\u00CE');
+ entities.Add ("Iuml", '\u00CF');
+ entities.Add ("ETH", '\u00D0');
+ entities.Add ("Ntilde", '\u00D1');
+ entities.Add ("Ograve", '\u00D2');
+ entities.Add ("Oacute", '\u00D3');
+ entities.Add ("Ocirc", '\u00D4');
+ entities.Add ("Otilde", '\u00D5');
+ entities.Add ("Ouml", '\u00D6');
+ entities.Add ("times", '\u00D7');
+ entities.Add ("Oslash", '\u00D8');
+ entities.Add ("Ugrave", '\u00D9');
+ entities.Add ("Uacute", '\u00DA');
+ entities.Add ("Ucirc", '\u00DB');
+ entities.Add ("Uuml", '\u00DC');
+ entities.Add ("Yacute", '\u00DD');
+ entities.Add ("THORN", '\u00DE');
+ entities.Add ("szlig", '\u00DF');
+ entities.Add ("agrave", '\u00E0');
+ entities.Add ("aacute", '\u00E1');
+ entities.Add ("acirc", '\u00E2');
+ entities.Add ("atilde", '\u00E3');
+ entities.Add ("auml", '\u00E4');
+ entities.Add ("aring", '\u00E5');
+ entities.Add ("aelig", '\u00E6');
+ entities.Add ("ccedil", '\u00E7');
+ entities.Add ("egrave", '\u00E8');
+ entities.Add ("eacute", '\u00E9');
+ entities.Add ("ecirc", '\u00EA');
+ entities.Add ("euml", '\u00EB');
+ entities.Add ("igrave", '\u00EC');
+ entities.Add ("iacute", '\u00ED');
+ entities.Add ("icirc", '\u00EE');
+ entities.Add ("iuml", '\u00EF');
+ entities.Add ("eth", '\u00F0');
+ entities.Add ("ntilde", '\u00F1');
+ entities.Add ("ograve", '\u00F2');
+ entities.Add ("oacute", '\u00F3');
+ entities.Add ("ocirc", '\u00F4');
+ entities.Add ("otilde", '\u00F5');
+ entities.Add ("ouml", '\u00F6');
+ entities.Add ("divide", '\u00F7');
+ entities.Add ("oslash", '\u00F8');
+ entities.Add ("ugrave", '\u00F9');
+ entities.Add ("uacute", '\u00FA');
+ entities.Add ("ucirc", '\u00FB');
+ entities.Add ("uuml", '\u00FC');
+ entities.Add ("yacute", '\u00FD');
+ entities.Add ("thorn", '\u00FE');
+ entities.Add ("yuml", '\u00FF');
+ entities.Add ("fnof", '\u0192');
+ entities.Add ("Alpha", '\u0391');
+ entities.Add ("Beta", '\u0392');
+ entities.Add ("Gamma", '\u0393');
+ entities.Add ("Delta", '\u0394');
+ entities.Add ("Epsilon", '\u0395');
+ entities.Add ("Zeta", '\u0396');
+ entities.Add ("Eta", '\u0397');
+ entities.Add ("Theta", '\u0398');
+ entities.Add ("Iota", '\u0399');
+ entities.Add ("Kappa", '\u039A');
+ entities.Add ("Lambda", '\u039B');
+ entities.Add ("Mu", '\u039C');
+ entities.Add ("Nu", '\u039D');
+ entities.Add ("Xi", '\u039E');
+ entities.Add ("Omicron", '\u039F');
+ entities.Add ("Pi", '\u03A0');
+ entities.Add ("Rho", '\u03A1');
+ entities.Add ("Sigma", '\u03A3');
+ entities.Add ("Tau", '\u03A4');
+ entities.Add ("Upsilon", '\u03A5');
+ entities.Add ("Phi", '\u03A6');
+ entities.Add ("Chi", '\u03A7');
+ entities.Add ("Psi", '\u03A8');
+ entities.Add ("Omega", '\u03A9');
+ entities.Add ("alpha", '\u03B1');
+ entities.Add ("beta", '\u03B2');
+ entities.Add ("gamma", '\u03B3');
+ entities.Add ("delta", '\u03B4');
+ entities.Add ("epsilon", '\u03B5');
+ entities.Add ("zeta", '\u03B6');
+ entities.Add ("eta", '\u03B7');
+ entities.Add ("theta", '\u03B8');
+ entities.Add ("iota", '\u03B9');
+ entities.Add ("kappa", '\u03BA');
+ entities.Add ("lambda", '\u03BB');
+ entities.Add ("mu", '\u03BC');
+ entities.Add ("nu", '\u03BD');
+ entities.Add ("xi", '\u03BE');
+ entities.Add ("omicron", '\u03BF');
+ entities.Add ("pi", '\u03C0');
+ entities.Add ("rho", '\u03C1');
+ entities.Add ("sigmaf", '\u03C2');
+ entities.Add ("sigma", '\u03C3');
+ entities.Add ("tau", '\u03C4');
+ entities.Add ("upsilon", '\u03C5');
+ entities.Add ("phi", '\u03C6');
+ entities.Add ("chi", '\u03C7');
+ entities.Add ("psi", '\u03C8');
+ entities.Add ("omega", '\u03C9');
+ entities.Add ("thetasym", '\u03D1');
+ entities.Add ("upsih", '\u03D2');
+ entities.Add ("piv", '\u03D6');
+ entities.Add ("bull", '\u2022');
+ entities.Add ("hellip", '\u2026');
+ entities.Add ("prime", '\u2032');
+ entities.Add ("Prime", '\u2033');
+ entities.Add ("oline", '\u203E');
+ entities.Add ("frasl", '\u2044');
+ entities.Add ("weierp", '\u2118');
+ entities.Add ("image", '\u2111');
+ entities.Add ("real", '\u211C');
+ entities.Add ("trade", '\u2122');
+ entities.Add ("alefsym", '\u2135');
+ entities.Add ("larr", '\u2190');
+ entities.Add ("uarr", '\u2191');
+ entities.Add ("rarr", '\u2192');
+ entities.Add ("darr", '\u2193');
+ entities.Add ("harr", '\u2194');
+ entities.Add ("crarr", '\u21B5');
+ entities.Add ("lArr", '\u21D0');
+ entities.Add ("uArr", '\u21D1');
+ entities.Add ("rArr", '\u21D2');
+ entities.Add ("dArr", '\u21D3');
+ entities.Add ("hArr", '\u21D4');
+ entities.Add ("forall", '\u2200');
+ entities.Add ("part", '\u2202');
+ entities.Add ("exist", '\u2203');
+ entities.Add ("empty", '\u2205');
+ entities.Add ("nabla", '\u2207');
+ entities.Add ("isin", '\u2208');
+ entities.Add ("notin", '\u2209');
+ entities.Add ("ni", '\u220B');
+ entities.Add ("prod", '\u220F');
+ entities.Add ("sum", '\u2211');
+ entities.Add ("minus", '\u2212');
+ entities.Add ("lowast", '\u2217');
+ entities.Add ("radic", '\u221A');
+ entities.Add ("prop", '\u221D');
+ entities.Add ("infin", '\u221E');
+ entities.Add ("ang", '\u2220');
+ entities.Add ("and", '\u2227');
+ entities.Add ("or", '\u2228');
+ entities.Add ("cap", '\u2229');
+ entities.Add ("cup", '\u222A');
+ entities.Add ("int", '\u222B');
+ entities.Add ("there4", '\u2234');
+ entities.Add ("sim", '\u223C');
+ entities.Add ("cong", '\u2245');
+ entities.Add ("asymp", '\u2248');
+ entities.Add ("ne", '\u2260');
+ entities.Add ("equiv", '\u2261');
+ entities.Add ("le", '\u2264');
+ entities.Add ("ge", '\u2265');
+ entities.Add ("sub", '\u2282');
+ entities.Add ("sup", '\u2283');
+ entities.Add ("nsub", '\u2284');
+ entities.Add ("sube", '\u2286');
+ entities.Add ("supe", '\u2287');
+ entities.Add ("oplus", '\u2295');
+ entities.Add ("otimes", '\u2297');
+ entities.Add ("perp", '\u22A5');
+ entities.Add ("sdot", '\u22C5');
+ entities.Add ("lceil", '\u2308');
+ entities.Add ("rceil", '\u2309');
+ entities.Add ("lfloor", '\u230A');
+ entities.Add ("rfloor", '\u230B');
+ entities.Add ("lang", '\u2329');
+ entities.Add ("rang", '\u232A');
+ entities.Add ("loz", '\u25CA');
+ entities.Add ("spades", '\u2660');
+ entities.Add ("clubs", '\u2663');
+ entities.Add ("hearts", '\u2665');
+ entities.Add ("diams", '\u2666');
+ entities.Add ("quot", '\u0022');
+ entities.Add ("amp", '\u0026');
+ entities.Add ("lt", '\u003C');
+ entities.Add ("gt", '\u003E');
+ entities.Add ("OElig", '\u0152');
+ entities.Add ("oelig", '\u0153');
+ entities.Add ("Scaron", '\u0160');
+ entities.Add ("scaron", '\u0161');
+ entities.Add ("Yuml", '\u0178');
+ entities.Add ("circ", '\u02C6');
+ entities.Add ("tilde", '\u02DC');
+ entities.Add ("ensp", '\u2002');
+ entities.Add ("emsp", '\u2003');
+ entities.Add ("thinsp", '\u2009');
+ entities.Add ("zwnj", '\u200C');
+ entities.Add ("zwj", '\u200D');
+ entities.Add ("lrm", '\u200E');
+ entities.Add ("rlm", '\u200F');
+ entities.Add ("ndash", '\u2013');
+ entities.Add ("mdash", '\u2014');
+ entities.Add ("lsquo", '\u2018');
+ entities.Add ("rsquo", '\u2019');
+ entities.Add ("sbquo", '\u201A');
+ entities.Add ("ldquo", '\u201C');
+ entities.Add ("rdquo", '\u201D');
+ entities.Add ("bdquo", '\u201E');
+ entities.Add ("dagger", '\u2020');
+ entities.Add ("Dagger", '\u2021');
+ entities.Add ("permil", '\u2030');
+ entities.Add ("lsaquo", '\u2039');
+ entities.Add ("rsaquo", '\u203A');
+ entities.Add ("euro", '\u20AC');
+ }
+
+ public HttpUtility ()
+ {
+ }
+
+ #endregion // Constructors
+
+ #region Methods
+
+ public static void HtmlAttributeEncode (string s, TextWriter output)
+ {
+ output.Write(HtmlAttributeEncode(s));
+ }
+
+ public static string HtmlAttributeEncode (string s)
+ {
+ if (null == s)
+ return null;
+
+ bool needEncode = false;
+ for (int i = 0; i < s.Length; i++) {
+ if (s [i] == '&' || s [i] == '"' || s [i] == '<') {
+ needEncode = true;
+ break;
+ }
+ }
+
+ if (!needEncode)
+ return s;
+
+ StringBuilder output = new StringBuilder ();
+ int len = s.Length;
+ for (int i = 0; i < len; i++)
+ switch (s [i]) {
+ case '&' :
+ output.Append ("&");
+ break;
+ case '"' :
+ output.Append (""");
+ break;
+ case '<':
+ output.Append ("<");
+ break;
+ default:
+ output.Append (s [i]);
+ break;
+ }
+
+ return output.ToString();
+ }
+
+ public static string UrlDecode (string str)
+ {
+ return UrlDecode(str, Encoding.UTF8);
+ }
+
+ static char [] GetChars (MemoryStream b, Encoding e)
+ {
+ return e.GetChars (b.GetBuffer (), 0, (int) b.Length);
+ }
+
+ static void WriteCharBytes (IList buf, char ch, Encoding e)
+ {
+ if (ch > 255) {
+ foreach (byte b in e.GetBytes (new char[] { ch }))
+ buf.Add (b);
+ } else
+ buf.Add ((byte)ch);
+ }
+
+ public static string UrlDecode (string s, Encoding e)
+ {
+ if (null == s)
+ return null;
+
+ if (s.IndexOf ('%') == -1 && s.IndexOf ('+') == -1)
+ return s;
+
+ if (e == null)
+ e = Encoding.UTF8;
+
+ long len = s.Length;
+ var bytes = new List ();
+ int xchar;
+ char ch;
+
+ for (int i = 0; i < len; i++) {
+ ch = s [i];
+ if (ch == '%' && i + 2 < len && s [i + 1] != '%') {
+ if (s [i + 1] == 'u' && i + 5 < len) {
+ // unicode hex sequence
+ xchar = GetChar (s, i + 2, 4);
+ if (xchar != -1) {
+ WriteCharBytes (bytes, (char)xchar, e);
+ i += 5;
+ } else
+ WriteCharBytes (bytes, '%', e);
+ } else if ((xchar = GetChar (s, i + 1, 2)) != -1) {
+ WriteCharBytes (bytes, (char)xchar, e);
+ i += 2;
+ } else {
+ WriteCharBytes (bytes, '%', e);
+ }
+ continue;
+ }
+
+ if (ch == '+')
+ WriteCharBytes (bytes, ' ', e);
+ else
+ WriteCharBytes (bytes, ch, e);
+ }
+
+ byte[] buf = bytes.ToArray ();
+ bytes = null;
+ return e.GetString (buf);
+
+ }
+
+ public static string UrlDecode (byte [] bytes, Encoding e)
+ {
+ if (bytes == null)
+ return null;
+
+ return UrlDecode (bytes, 0, bytes.Length, e);
+ }
+
+ static int GetInt (byte b)
+ {
+ char c = (char) b;
+ if (c >= '0' && c <= '9')
+ return c - '0';
+
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+
+ return -1;
+ }
+
+ static int GetChar (byte [] bytes, int offset, int length)
+ {
+ int value = 0;
+ int end = length + offset;
+ for (int i = offset; i < end; i++) {
+ int current = GetInt (bytes [i]);
+ if (current == -1)
+ return -1;
+ value = (value << 4) + current;
+ }
+
+ return value;
+ }
+
+ static int GetChar (string str, int offset, int length)
+ {
+ int val = 0;
+ int end = length + offset;
+ for (int i = offset; i < end; i++) {
+ char c = str [i];
+ if (c > 127)
+ return -1;
+
+ int current = GetInt ((byte) c);
+ if (current == -1)
+ return -1;
+ val = (val << 4) + current;
+ }
+
+ return val;
+ }
+
+ public static string UrlDecode (byte [] bytes, int offset, int count, Encoding e)
+ {
+ if (bytes == null)
+ return null;
+ if (count == 0)
+ return String.Empty;
+
+ if (bytes == null)
+ throw new ArgumentNullException ("bytes");
+
+ if (offset < 0 || offset > bytes.Length)
+ throw new ArgumentOutOfRangeException ("offset");
+
+ if (count < 0 || offset + count > bytes.Length)
+ throw new ArgumentOutOfRangeException ("count");
+
+ StringBuilder output = new StringBuilder ();
+ MemoryStream acc = new MemoryStream ();
+
+ int end = count + offset;
+ int xchar;
+ for (int i = offset; i < end; i++) {
+ if (bytes [i] == '%' && i + 2 < count && bytes [i + 1] != '%') {
+ if (bytes [i + 1] == (byte) 'u' && i + 5 < end) {
+ if (acc.Length > 0) {
+ output.Append (GetChars (acc, e));
+ acc.SetLength (0);
+ }
+ xchar = GetChar (bytes, i + 2, 4);
+ if (xchar != -1) {
+ output.Append ((char) xchar);
+ i += 5;
+ continue;
+ }
+ } else if ((xchar = GetChar (bytes, i + 1, 2)) != -1) {
+ acc.WriteByte ((byte) xchar);
+ i += 2;
+ continue;
+ }
+ }
+
+ if (acc.Length > 0) {
+ output.Append (GetChars (acc, e));
+ acc.SetLength (0);
+ }
+
+ if (bytes [i] == '+') {
+ output.Append (' ');
+ } else {
+ output.Append ((char) bytes [i]);
+ }
+ }
+
+ if (acc.Length > 0) {
+ output.Append (GetChars (acc, e));
+ }
+
+ acc = null;
+ return output.ToString ();
+ }
+
+ public static byte [] UrlDecodeToBytes (byte [] bytes)
+ {
+ if (bytes == null)
+ return null;
+
+ return UrlDecodeToBytes (bytes, 0, bytes.Length);
+ }
+
+ public static byte [] UrlDecodeToBytes (string str)
+ {
+ return UrlDecodeToBytes (str, Encoding.UTF8);
+ }
+
+ public static byte [] UrlDecodeToBytes (string str, Encoding e)
+ {
+ if (str == null)
+ return null;
+
+ if (e == null)
+ throw new ArgumentNullException ("e");
+
+ return UrlDecodeToBytes (e.GetBytes (str));
+ }
+
+ public static byte [] UrlDecodeToBytes (byte [] bytes, int offset, int count)
+ {
+ if (bytes == null)
+ return null;
+ if (count == 0)
+ return new byte [0];
+
+ int len = bytes.Length;
+ if (offset < 0 || offset >= len)
+ throw new ArgumentOutOfRangeException("offset");
+
+ if (count < 0 || offset > len - count)
+ throw new ArgumentOutOfRangeException("count");
+
+ MemoryStream result = new MemoryStream ();
+ int end = offset + count;
+ for (int i = offset; i < end; i++){
+ char c = (char) bytes [i];
+ if (c == '+') {
+ c = ' ';
+ } else if (c == '%' && i < end - 2) {
+ int xchar = GetChar (bytes, i + 1, 2);
+ if (xchar != -1) {
+ c = (char) xchar;
+ i += 2;
+ }
+ }
+ result.WriteByte ((byte) c);
+ }
+
+ return result.ToArray ();
+ }
+
+ public static string UrlEncode(string str)
+ {
+ return UrlEncode(str, Encoding.UTF8);
+ }
+
+ public static string UrlEncode (string s, Encoding Enc)
+ {
+ if (s == null)
+ return null;
+
+ if (s == "")
+ return "";
+
+ bool needEncode = false;
+ int len = s.Length;
+ for (int i = 0; i < len; i++) {
+ char c = s [i];
+ if ((c < '0') || (c < 'A' && c > '9') || (c > 'Z' && c < 'a') || (c > 'z')) {
+ if (NotEncoded (c))
+ continue;
+
+ needEncode = true;
+ break;
+ }
+ }
+
+ if (!needEncode)
+ return s;
+
+ // avoided GetByteCount call
+ byte [] bytes = new byte[Enc.GetMaxByteCount(s.Length)];
+ int realLen = Enc.GetBytes (s, 0, s.Length, bytes, 0);
+ return Encoding.ASCII.GetString (UrlEncodeToBytes (bytes, 0, realLen));
+ }
+
+ public static string UrlEncode (byte [] bytes)
+ {
+ if (bytes == null)
+ return null;
+
+ if (bytes.Length == 0)
+ return "";
+
+ return Encoding.ASCII.GetString (UrlEncodeToBytes (bytes, 0, bytes.Length));
+ }
+
+ public static string UrlEncode (byte [] bytes, int offset, int count)
+ {
+ if (bytes == null)
+ return null;
+
+ if (bytes.Length == 0)
+ return "";
+
+ return Encoding.ASCII.GetString (UrlEncodeToBytes (bytes, offset, count));
+ }
+
+ public static byte [] UrlEncodeToBytes (string str)
+ {
+ return UrlEncodeToBytes (str, Encoding.UTF8);
+ }
+
+ public static byte [] UrlEncodeToBytes (string str, Encoding e)
+ {
+ if (str == null)
+ return null;
+
+ if (str == "")
+ return new byte [0];
+
+ byte [] bytes = e.GetBytes (str);
+ return UrlEncodeToBytes (bytes, 0, bytes.Length);
+ }
+
+ public static byte [] UrlEncodeToBytes (byte [] bytes)
+ {
+ if (bytes == null)
+ return null;
+
+ if (bytes.Length == 0)
+ return new byte [0];
+
+ return UrlEncodeToBytes (bytes, 0, bytes.Length);
+ }
+
+ static char [] hexChars = "0123456789abcdef".ToCharArray ();
+
+ static bool NotEncoded (char c)
+ {
+ return (c == '!' || c == '\'' || c == '(' || c == ')' || c == '*' || c == '-' || c == '.' || c == '_');
+ }
+
+ static void UrlEncodeChar (char c, Stream result, bool isUnicode) {
+ if (c > 255) {
+ //FIXME: what happens when there is an internal error?
+ //if (!isUnicode)
+ // throw new ArgumentOutOfRangeException ("c", c, "c must be less than 256");
+ int idx;
+ int i = (int) c;
+
+ result.WriteByte ((byte)'%');
+ result.WriteByte ((byte)'u');
+ idx = i >> 12;
+ result.WriteByte ((byte)hexChars [idx]);
+ idx = (i >> 8) & 0x0F;
+ result.WriteByte ((byte)hexChars [idx]);
+ idx = (i >> 4) & 0x0F;
+ result.WriteByte ((byte)hexChars [idx]);
+ idx = i & 0x0F;
+ result.WriteByte ((byte)hexChars [idx]);
+ return;
+ }
+
+ if (c > ' ' && NotEncoded (c)) {
+ result.WriteByte ((byte)c);
+ return;
+ }
+ if (c==' ') {
+ result.WriteByte ((byte)'+');
+ return;
+ }
+ if ( (c < '0') ||
+ (c < 'A' && c > '9') ||
+ (c > 'Z' && c < 'a') ||
+ (c > 'z')) {
+ if (isUnicode && c > 127) {
+ result.WriteByte ((byte)'%');
+ result.WriteByte ((byte)'u');
+ result.WriteByte ((byte)'0');
+ result.WriteByte ((byte)'0');
+ }
+ else
+ result.WriteByte ((byte)'%');
+
+ int idx = ((int) c) >> 4;
+ result.WriteByte ((byte)hexChars [idx]);
+ idx = ((int) c) & 0x0F;
+ result.WriteByte ((byte)hexChars [idx]);
+ }
+ else
+ result.WriteByte ((byte)c);
+ }
+
+ public static byte [] UrlEncodeToBytes (byte [] bytes, int offset, int count)
+ {
+ if (bytes == null)
+ return null;
+
+ int len = bytes.Length;
+ if (len == 0)
+ return new byte [0];
+
+ if (offset < 0 || offset >= len)
+ throw new ArgumentOutOfRangeException("offset");
+
+ if (count < 0 || count > len - offset)
+ throw new ArgumentOutOfRangeException("count");
+
+ MemoryStream result = new MemoryStream (count);
+ int end = offset + count;
+ for (int i = offset; i < end; i++)
+ UrlEncodeChar ((char)bytes [i], result, false);
+
+ return result.ToArray();
+ }
+
+ public static string UrlEncodeUnicode (string str)
+ {
+ if (str == null)
+ return null;
+
+ return Encoding.ASCII.GetString (UrlEncodeUnicodeToBytes (str));
+ }
+
+ public static byte [] UrlEncodeUnicodeToBytes (string str)
+ {
+ if (str == null)
+ return null;
+
+ if (str == "")
+ return new byte [0];
+
+ MemoryStream result = new MemoryStream (str.Length);
+ foreach (char c in str){
+ UrlEncodeChar (c, result, true);
+ }
+ return result.ToArray ();
+ }
+
+ ///
+ /// Decodes an HTML-encoded string and returns the decoded string.
+ ///
+ /// The HTML string to decode.
+ /// The decoded text.
+ public static string HtmlDecode (string s)
+ {
+ if (s == null)
+ throw new ArgumentNullException ("s");
+
+ if (s.IndexOf ('&') == -1)
+ return s;
+
+ StringBuilder entity = new StringBuilder ();
+ StringBuilder output = new StringBuilder ();
+ int len = s.Length;
+ // 0 -> nothing,
+ // 1 -> right after '&'
+ // 2 -> between '&' and ';' but no '#'
+ // 3 -> '#' found after '&' and getting numbers
+ int state = 0;
+ int number = 0;
+ bool have_trailing_digits = false;
+
+ for (int i = 0; i < len; i++) {
+ char c = s [i];
+ if (state == 0) {
+ if (c == '&') {
+ entity.Append (c);
+ state = 1;
+ } else {
+ output.Append (c);
+ }
+ continue;
+ }
+
+ if (c == '&') {
+ state = 1;
+ if (have_trailing_digits) {
+ entity.Append (number.ToString (CultureInfo.InvariantCulture));
+ have_trailing_digits = false;
+ }
+
+ output.Append (entity.ToString ());
+ entity.Length = 0;
+ entity.Append ('&');
+ continue;
+ }
+
+ if (state == 1) {
+ if (c == ';') {
+ state = 0;
+ output.Append (entity.ToString ());
+ output.Append (c);
+ entity.Length = 0;
+ } else {
+ number = 0;
+ if (c != '#') {
+ state = 2;
+ } else {
+ state = 3;
+ }
+ entity.Append (c);
+ }
+ } else if (state == 2) {
+ entity.Append (c);
+ if (c == ';') {
+ string key = entity.ToString ();
+ if (key.Length > 1 && Entities.ContainsKey (key.Substring (1, key.Length - 2)))
+ key = Entities [key.Substring (1, key.Length - 2)].ToString ();
+
+ output.Append (key);
+ state = 0;
+ entity.Length = 0;
+ }
+ } else if (state == 3) {
+ if (c == ';') {
+ if (number > 65535) {
+ output.Append ("");
+ output.Append (number.ToString (CultureInfo.InvariantCulture));
+ output.Append (";");
+ } else {
+ output.Append ((char) number);
+ }
+ state = 0;
+ entity.Length = 0;
+ have_trailing_digits = false;
+ } else if (Char.IsDigit (c)) {
+ number = number * 10 + ((int) c - '0');
+ have_trailing_digits = true;
+ } else {
+ state = 2;
+ if (have_trailing_digits) {
+ entity.Append (number.ToString (CultureInfo.InvariantCulture));
+ have_trailing_digits = false;
+ }
+ entity.Append (c);
+ }
+ }
+ }
+
+ if (entity.Length > 0) {
+ output.Append (entity.ToString ());
+ } else if (have_trailing_digits) {
+ output.Append (number.ToString (CultureInfo.InvariantCulture));
+ }
+ return output.ToString ();
+ }
+
+ ///
+ /// Decodes an HTML-encoded string and sends the resulting output to a TextWriter output stream.
+ ///
+ /// The HTML string to decode
+ /// The TextWriter output stream containing the decoded string.
+ public static void HtmlDecode(string s, TextWriter output)
+ {
+ if (s != null)
+ output.Write (HtmlDecode (s));
+ }
+
+ ///
+ /// HTML-encodes a string and returns the encoded string.
+ ///
+ /// The text string to encode.
+ /// The HTML-encoded text.
+ public static string HtmlEncode (string s)
+ {
+ if (s == null)
+ return null;
+
+ bool needEncode = false;
+ for (int i = 0; i < s.Length; i++) {
+ char c = s [i];
+ if (c == '&' || c == '"' || c == '<' || c == '>' || c > 159) {
+ needEncode = true;
+ break;
+ }
+ }
+
+ if (!needEncode)
+ return s;
+
+ StringBuilder output = new StringBuilder ();
+
+ int len = s.Length;
+ for (int i = 0; i < len; i++)
+ switch (s [i]) {
+ case '&' :
+ output.Append ("&");
+ break;
+ case '>' :
+ output.Append (">");
+ break;
+ case '<' :
+ output.Append ("<");
+ break;
+ case '"' :
+ output.Append (""");
+ break;
+ default:
+ // MS starts encoding with from 160 and stops at 255.
+ // We don't do that. One reason is the 65308/65310 unicode
+ // characters that look like '<' and '>'.
+#if TARGET_JVM
+ if (s [i] > 159 && s [i] < 256) {
+#else
+ if (s [i] > 159) {
+#endif
+ output.Append ("");
+ output.Append (((int) s [i]).ToString (CultureInfo.InvariantCulture));
+ output.Append (";");
+ } else {
+ output.Append (s [i]);
+ }
+ break;
+ }
+ return output.ToString ();
+ }
+
+ ///
+ /// HTML-encodes a string and sends the resulting output to a TextWriter output stream.
+ ///
+ /// The string to encode.
+ /// The TextWriter output stream containing the encoded string.
+ public static void HtmlEncode(string s, TextWriter output)
+ {
+ if (s != null)
+ output.Write (HtmlEncode (s));
+ }
+
+ public static string UrlPathEncode (string s)
+ {
+ if (s == null || s.Length == 0)
+ return s;
+
+ MemoryStream result = new MemoryStream ();
+ int length = s.Length;
+ for (int i = 0; i < length; i++) {
+ UrlPathEncodeChar (s [i], result);
+ }
+ return Encoding.ASCII.GetString (result.ToArray ());
+ }
+
+ static void UrlPathEncodeChar (char c, Stream result)
+ {
+ if (c < 33 || c > 126) {
+ byte [] bIn = Encoding.UTF8.GetBytes (c.ToString ());
+ for (int i = 0; i < bIn.Length; i++) {
+ result.WriteByte ((byte) '%');
+ int idx = ((int) bIn [i]) >> 4;
+ result.WriteByte ((byte) hexChars [idx]);
+ idx = ((int) bIn [i]) & 0x0F;
+ result.WriteByte ((byte) hexChars [idx]);
+ }
+ }
+ else if (c == ' ') {
+ result.WriteByte ((byte) '%');
+ result.WriteByte ((byte) '2');
+ result.WriteByte ((byte) '0');
+ }
+ else
+ result.WriteByte ((byte) c);
+ }
+
+ public static NameValueCollection ParseQueryString (string query)
+ {
+ return ParseQueryString (query, Encoding.UTF8);
+ }
+
+ public static NameValueCollection ParseQueryString (string query, Encoding encoding)
+ {
+ if (query == null)
+ throw new ArgumentNullException ("query");
+ if (encoding == null)
+ throw new ArgumentNullException ("encoding");
+ if (query.Length == 0 || (query.Length == 1 && query[0] == '?'))
+ return new NameValueCollection ();
+ if (query[0] == '?')
+ query = query.Substring (1);
+
+ NameValueCollection result = new HttpQSCollection ();
+ ParseQueryString (query, encoding, result);
+ return result;
+ }
+
+ internal static void ParseQueryString (string query, Encoding encoding, NameValueCollection result)
+ {
+ if (query.Length == 0)
+ return;
+
+ string decoded = HtmlDecode (query);
+ int decodedLength = decoded.Length;
+ int namePos = 0;
+ bool first = true;
+ while (namePos <= decodedLength) {
+ int valuePos = -1, valueEnd = -1;
+ for (int q = namePos; q < decodedLength; q++) {
+ if (valuePos == -1 && decoded [q] == '=') {
+ valuePos = q + 1;
+ } else if (decoded [q] == '&') {
+ valueEnd = q;
+ break;
+ }
+ }
+
+ if (first) {
+ first = false;
+ if (decoded [namePos] == '?')
+ namePos++;
+ }
+
+ string name, value;
+ if (valuePos == -1) {
+ name = null;
+ valuePos = namePos;
+ } else {
+ name = UrlDecode (decoded.Substring (namePos, valuePos - namePos - 1), encoding);
+ }
+ if (valueEnd < 0) {
+ namePos = -1;
+ valueEnd = decoded.Length;
+ } else {
+ namePos = valueEnd + 1;
+ }
+ value = UrlDecode (decoded.Substring (valuePos, valueEnd - valuePos), encoding);
+
+ result.Add (name, value);
+ if (namePos == -1)
+ break;
+ }
+ }
+ #endregion // Methods
+ }
+}
+
diff --git a/websocket-sharp/Net/HttpVersion.cs b/websocket-sharp/Net/HttpVersion.cs
new file mode 100644
index 00000000..570009ad
--- /dev/null
+++ b/websocket-sharp/Net/HttpVersion.cs
@@ -0,0 +1,42 @@
+//
+// HttpVersion.cs
+// Copied from System.Net.HttpVersion
+//
+// Author:
+// Lawrence Pit (loz@cable.a2000.nl)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace WebSocketSharp.Net {
+
+ //
+ //
+ public class HttpVersion {
+
+ public static readonly Version Version10 = new Version (1, 0);
+ public static readonly Version Version11 = new Version (1, 1);
+
+ // pretty useless..
+ public HttpVersion () {}
+ }
+}
diff --git a/websocket-sharp/Net/ListenerAsyncResult.cs b/websocket-sharp/Net/ListenerAsyncResult.cs
new file mode 100644
index 00000000..78f64438
--- /dev/null
+++ b/websocket-sharp/Net/ListenerAsyncResult.cs
@@ -0,0 +1,186 @@
+//
+// ListenerAsyncResult.cs
+// Copied from System.Net.ListenerAsyncResult
+//
+// Authors:
+// Gonzalo Paniagua Javier (gonzalo@ximian.com)
+//
+// Copyright (c) 2005 Ximian, Inc (http://www.ximian.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Net;
+using System.Threading;
+
+namespace WebSocketSharp.Net {
+
+ class ListenerAsyncResult : IAsyncResult
+ {
+ static WaitCallback InvokeCB = new WaitCallback (InvokeCallback);
+
+ AsyncCallback cb;
+ bool completed;
+ HttpListenerContext context;
+ Exception exception;
+ ListenerAsyncResult forward;
+ ManualResetEvent handle;
+ object locker;
+ object state;
+ bool synch;
+
+ internal bool EndCalled;
+ internal bool InGet;
+
+ public ListenerAsyncResult (AsyncCallback cb, object state)
+ {
+ this.cb = cb;
+ this.state = state;
+ this.locker = new object();
+ }
+
+ public object AsyncState {
+ get {
+ if (forward != null)
+ return forward.AsyncState;
+ return state;
+ }
+ }
+
+ public WaitHandle AsyncWaitHandle {
+ get {
+ if (forward != null)
+ return forward.AsyncWaitHandle;
+
+ lock (locker) {
+ if (handle == null)
+ handle = new ManualResetEvent (completed);
+ }
+
+ return handle;
+ }
+ }
+
+ public bool CompletedSynchronously {
+ get {
+ if (forward != null)
+ return forward.CompletedSynchronously;
+ return synch;
+ }
+
+ }
+
+ public bool IsCompleted {
+ get {
+ if (forward != null)
+ return forward.IsCompleted;
+
+ lock (locker) {
+ return completed;
+ }
+ }
+ }
+
+ static void InvokeCallback (object o)
+ {
+ ListenerAsyncResult ares = (ListenerAsyncResult) o;
+ if (ares.forward != null) {
+ InvokeCallback (ares.forward);
+ return;
+ }
+ try {
+ ares.cb (ares);
+ } catch {
+ }
+ }
+
+ internal void Complete (Exception exc)
+ {
+ if (forward != null) {
+ forward.Complete (exc);
+ return;
+ }
+ exception = exc;
+ if (InGet && (exc is ObjectDisposedException))
+ exception = new HttpListenerException (500, "Listener closed");
+ lock (locker) {
+ completed = true;
+ if (handle != null)
+ handle.Set ();
+
+ if (cb != null)
+ ThreadPool.UnsafeQueueUserWorkItem (InvokeCB, this);
+ }
+ }
+
+ internal void Complete (HttpListenerContext context)
+ {
+ Complete (context, false);
+ }
+
+ internal void Complete (HttpListenerContext context, bool synch)
+ {
+ if (forward != null) {
+ forward.Complete (context, synch);
+ return;
+ }
+ this.synch = synch;
+ this.context = context;
+ lock (locker) {
+ AuthenticationSchemes schemes = context.Listener.SelectAuthenticationScheme (context);
+ if ((schemes == AuthenticationSchemes.Basic || context.Listener.AuthenticationSchemes == AuthenticationSchemes.Negotiate) && context.Request.Headers ["Authorization"] == null) {
+ context.Response.StatusCode = 401;
+ context.Response.Headers ["WWW-Authenticate"] = schemes + " realm=\"" + context.Listener.Realm + "\"";
+ context.Response.OutputStream.Close ();
+ IAsyncResult ares = context.Listener.BeginGetContext (cb, state);
+ this.forward = (ListenerAsyncResult) ares;
+ lock (forward.locker) {
+ if (handle != null)
+ forward.handle = handle;
+ }
+ ListenerAsyncResult next = forward;
+ for (int i = 0; next.forward != null; i++) {
+ if (i > 20)
+ Complete (new HttpListenerException (400, "Too many authentication errors"));
+ next = next.forward;
+ }
+ } else {
+ completed = true;
+ if (handle != null)
+ handle.Set ();
+
+ if (cb != null)
+ ThreadPool.UnsafeQueueUserWorkItem (InvokeCB, this);
+ }
+ }
+ }
+
+ internal HttpListenerContext GetContext ()
+ {
+ if (forward != null)
+ return forward.GetContext ();
+ if (exception != null)
+ throw exception;
+
+ return context;
+ }
+ }
+}
diff --git a/websocket-sharp/Net/ListenerPrefix.cs b/websocket-sharp/Net/ListenerPrefix.cs
new file mode 100644
index 00000000..41f4d590
--- /dev/null
+++ b/websocket-sharp/Net/ListenerPrefix.cs
@@ -0,0 +1,166 @@
+//
+// ListenerPrefix.cs
+// Copied from System.ListenerPrefix
+//
+// Author:
+// Gonzalo Paniagua Javier (gonzalo@novell.com)
+// Oleg Mihailik (mihailik gmail co_m)
+//
+// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Net;
+
+namespace WebSocketSharp.Net {
+
+ sealed class ListenerPrefix {
+
+ IPAddress [] addresses;
+ string host;
+ string original;
+ string path;
+ ushort port;
+ bool secure;
+
+ public HttpListener Listener;
+
+ public ListenerPrefix (string prefix)
+ {
+ original = prefix;
+ Parse (prefix);
+ }
+
+ public IPAddress [] Addresses {
+ get { return addresses; }
+ set { addresses = value; }
+ }
+
+ public string Host {
+ get { return host; }
+ }
+
+ public int Port {
+ get { return (int) port; }
+ }
+
+ public string Path {
+ get { return path; }
+ }
+
+ public bool Secure {
+ get { return secure; }
+ }
+
+ void Parse (string uri)
+ {
+ int default_port = (uri.StartsWith ("http://")) ? 80 : -1;
+ if (default_port == -1) {
+ default_port = (uri.StartsWith ("https://")) ? 443 : -1;
+ secure = true;
+ }
+
+ int length = uri.Length;
+ int start_host = uri.IndexOf (':') + 3;
+ if (start_host >= length)
+ throw new ArgumentException ("No host specified.");
+
+ int colon = uri.IndexOf (':', start_host, length - start_host);
+ int root;
+ if (colon > 0) {
+ host = uri.Substring (start_host, colon - start_host);
+ root = uri.IndexOf ('/', colon, length - colon);
+ port = (ushort) Int32.Parse (uri.Substring (colon + 1, root - colon - 1));
+ path = uri.Substring (root);
+ } else {
+ root = uri.IndexOf ('/', start_host, length - start_host);
+ host = uri.Substring (start_host, root - start_host);
+ path = uri.Substring (root);
+ }
+ if (path.Length != 1)
+ path = path.Substring (0, path.Length - 1);
+ }
+
+ public static void CheckUri (string uri)
+ {
+ if (uri == null)
+ throw new ArgumentNullException ("uriPrefix");
+
+ int default_port = (uri.StartsWith ("http://")) ? 80 : -1;
+ if (default_port == -1)
+ default_port = (uri.StartsWith ("https://")) ? 443 : -1;
+ if (default_port == -1)
+ throw new ArgumentException ("Only 'http' and 'https' schemes are supported.");
+
+ int length = uri.Length;
+ int start_host = uri.IndexOf (':') + 3;
+ if (start_host >= length)
+ throw new ArgumentException ("No host specified.");
+
+ int colon = uri.IndexOf (':', start_host, length - start_host);
+ if (start_host == colon)
+ throw new ArgumentException ("No host specified.");
+
+ int root;
+ if (colon > 0) {
+ root = uri.IndexOf ('/', colon, length - colon);
+ if (root == -1)
+ throw new ArgumentException ("No path specified.");
+
+ try {
+ int p = Int32.Parse (uri.Substring (colon + 1, root - colon - 1));
+ if (p <= 0 || p >= 65536)
+ throw new Exception ();
+ } catch {
+ throw new ArgumentException ("Invalid port.");
+ }
+ } else {
+ root = uri.IndexOf ('/', start_host, length - start_host);
+ if (root == -1)
+ throw new ArgumentException ("No path specified.");
+ }
+
+ if (uri [uri.Length - 1] != '/')
+ throw new ArgumentException ("The prefix must end with '/'");
+ }
+
+ // Equals and GetHashCode are required to detect duplicates in HttpListenerPrefixCollection.
+ public override bool Equals (object o)
+ {
+ ListenerPrefix other = o as ListenerPrefix;
+ if (other == null)
+ return false;
+
+ return (original == other.original);
+ }
+
+ public override int GetHashCode ()
+ {
+ return original.GetHashCode ();
+ }
+
+ public override string ToString ()
+ {
+ return original;
+ }
+ }
+}
diff --git a/websocket-sharp/Net/RequestStream.cs b/websocket-sharp/Net/RequestStream.cs
new file mode 100644
index 00000000..2b08ea40
--- /dev/null
+++ b/websocket-sharp/Net/RequestStream.cs
@@ -0,0 +1,227 @@
+//
+// RequestStream.cs
+// Copied from System.Net.RequestStream
+//
+// Author:
+// Gonzalo Paniagua Javier (gonzalo@novell.com)
+//
+// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+
+namespace WebSocketSharp.Net {
+
+ class RequestStream : Stream
+ {
+ byte [] buffer;
+ int offset;
+ int length;
+ long remaining_body;
+ bool disposed;
+ System.IO.Stream stream;
+
+ internal RequestStream (System.IO.Stream stream, byte [] buffer, int offset, int length)
+ : this (stream, buffer, offset, length, -1)
+ {
+ }
+
+ internal RequestStream (System.IO.Stream stream, byte [] buffer, int offset, int length, long contentlength)
+ {
+ this.stream = stream;
+ this.buffer = buffer;
+ this.offset = offset;
+ this.length = length;
+ this.remaining_body = contentlength;
+ }
+
+ public override bool CanRead {
+ get { return true; }
+ }
+
+ public override bool CanSeek {
+ get { return false; }
+ }
+
+ public override bool CanWrite {
+ get { return false; }
+ }
+
+ public override long Length {
+ get { throw new NotSupportedException (); }
+ }
+
+ public override long Position {
+ get { throw new NotSupportedException (); }
+ set { throw new NotSupportedException (); }
+ }
+
+
+ public override void Close ()
+ {
+ disposed = true;
+ }
+
+ public override void Flush ()
+ {
+ }
+
+
+ // Returns 0 if we can keep reading from the base stream,
+ // > 0 if we read something from the buffer.
+ // -1 if we had a content length set and we finished reading that many bytes.
+ int FillFromBuffer (byte [] buffer, int off, int count)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException ("buffer");
+ if (off < 0)
+ throw new ArgumentOutOfRangeException ("offset", "< 0");
+ if (count < 0)
+ throw new ArgumentOutOfRangeException ("count", "< 0");
+ int len = buffer.Length;
+ if (off > len)
+ throw new ArgumentException ("destination offset is beyond array size");
+ if (off > len - count)
+ throw new ArgumentException ("Reading would overrun buffer");
+
+ if (this.remaining_body == 0)
+ return -1;
+
+ if (this.length == 0)
+ return 0;
+
+ int size = Math.Min (this.length, count);
+ if (this.remaining_body > 0)
+ size = (int) Math.Min (size, this.remaining_body);
+
+ if (this.offset > this.buffer.Length - size) {
+ size = Math.Min (size, this.buffer.Length - this.offset);
+ }
+ if (size == 0)
+ return 0;
+
+ Buffer.BlockCopy (this.buffer, this.offset, buffer, off, size);
+ this.offset += size;
+ this.length -= size;
+ if (this.remaining_body > 0)
+ remaining_body -= size;
+ return size;
+ }
+
+ public override int Read ([In,Out] byte[] buffer, int offset, int count)
+ {
+ if (disposed)
+ throw new ObjectDisposedException (typeof (RequestStream).ToString ());
+
+ // Call FillFromBuffer to check for buffer boundaries even when remaining_body is 0
+ int nread = FillFromBuffer (buffer, offset, count);
+ if (nread == -1) { // No more bytes available (Content-Length)
+ return 0;
+ } else if (nread > 0) {
+ return nread;
+ }
+
+ nread = stream.Read (buffer, offset, count);
+ if (nread > 0 && remaining_body > 0)
+ remaining_body -= nread;
+ return nread;
+ }
+
+ public override IAsyncResult BeginRead (byte [] buffer, int offset, int count,
+ AsyncCallback cback, object state)
+ {
+ if (disposed)
+ throw new ObjectDisposedException (typeof (RequestStream).ToString ());
+
+ int nread = FillFromBuffer (buffer, offset, count);
+ if (nread > 0 || nread == -1) {
+ HttpStreamAsyncResult ares = new HttpStreamAsyncResult ();
+ ares.Buffer = buffer;
+ ares.Offset = offset;
+ ares.Count = count;
+ ares.Callback = cback;
+ ares.State = state;
+ ares.SynchRead = nread;
+ ares.Complete ();
+ return ares;
+ }
+
+ // Avoid reading past the end of the request to allow
+ // for HTTP pipelining
+ if (remaining_body >= 0 && count > remaining_body)
+ count = (int) Math.Min (Int32.MaxValue, remaining_body);
+ return stream.BeginRead (buffer, offset, count, cback, state);
+ }
+
+ public override int EndRead (IAsyncResult ares)
+ {
+ if (disposed)
+ throw new ObjectDisposedException (typeof (RequestStream).ToString ());
+
+ if (ares == null)
+ throw new ArgumentNullException ("async_result");
+
+ if (ares is HttpStreamAsyncResult) {
+ HttpStreamAsyncResult r = (HttpStreamAsyncResult) ares;
+ if (!ares.IsCompleted)
+ ares.AsyncWaitHandle.WaitOne ();
+ return r.SynchRead;
+ }
+
+ // Close on exception?
+ int nread = stream.EndRead (ares);
+ if (remaining_body > 0 && nread > 0)
+ remaining_body -= nread;
+ return nread;
+ }
+
+ public override long Seek (long offset, SeekOrigin origin)
+ {
+ throw new NotSupportedException ();
+ }
+
+ public override void SetLength (long value)
+ {
+ throw new NotSupportedException ();
+ }
+
+ public override void Write (byte[] buffer, int offset, int count)
+ {
+ throw new NotSupportedException ();
+ }
+
+ public override IAsyncResult BeginWrite (byte [] buffer, int offset, int count,
+ AsyncCallback cback, object state)
+ {
+ throw new NotSupportedException ();
+ }
+
+ public override void EndWrite (IAsyncResult async_result)
+ {
+ throw new NotSupportedException ();
+ }
+ }
+}
diff --git a/websocket-sharp/Net/ResponseStream.cs b/websocket-sharp/Net/ResponseStream.cs
new file mode 100644
index 00000000..b3ccb5c4
--- /dev/null
+++ b/websocket-sharp/Net/ResponseStream.cs
@@ -0,0 +1,243 @@
+//
+// ResponseStream.cs
+// Copied from System.Net.ResponseStream
+//
+// Author:
+// Gonzalo Paniagua Javier (gonzalo@novell.com)
+//
+// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Runtime.InteropServices;
+
+namespace WebSocketSharp.Net {
+
+ // FIXME: Does this buffer the response until Close?
+ // Update: we send a single packet for the first non-chunked Write
+ // What happens when we set content-length to X and write X-1 bytes then close?
+ // what if we don't set content-length at all?
+ class ResponseStream : Stream
+ {
+
+ HttpListenerResponse response;
+ bool ignore_errors;
+ bool disposed;
+ bool trailer_sent;
+ System.IO.Stream stream;
+
+ internal ResponseStream (System.IO.Stream stream, HttpListenerResponse response, bool ignore_errors)
+ {
+ this.response = response;
+ this.ignore_errors = ignore_errors;
+ this.stream = stream;
+ }
+
+ public override bool CanRead {
+ get { return false; }
+ }
+
+ public override bool CanSeek {
+ get { return false; }
+ }
+
+ public override bool CanWrite {
+ get { return true; }
+ }
+
+ public override long Length {
+ get { throw new NotSupportedException (); }
+ }
+
+ public override long Position {
+ get { throw new NotSupportedException (); }
+ set { throw new NotSupportedException (); }
+ }
+
+
+ public override void Close ()
+ {
+ if (disposed == false) {
+ disposed = true;
+ byte [] bytes = null;
+ MemoryStream ms = GetHeaders (true);
+ bool chunked = response.SendChunked;
+ if (ms != null) {
+ long start = ms.Position;
+ if (chunked && !trailer_sent) {
+ bytes = GetChunkSizeBytes (0, true);
+ ms.Position = ms.Length;
+ ms.Write (bytes, 0, bytes.Length);
+ }
+ InternalWrite (ms.GetBuffer (), (int) start, (int) (ms.Length - start));
+ trailer_sent = true;
+ } else if (chunked && !trailer_sent) {
+ bytes = GetChunkSizeBytes (0, true);
+ InternalWrite (bytes, 0, bytes.Length);
+ trailer_sent = true;
+ }
+ response.Close ();
+ }
+ }
+
+ MemoryStream GetHeaders (bool closing)
+ {
+ if (response.HeadersSent)
+ return null;
+ MemoryStream ms = new MemoryStream ();
+ response.SendHeaders (closing, ms);
+ return ms;
+ }
+
+ public override void Flush ()
+ {
+ }
+
+ static byte [] crlf = new byte [] { 13, 10 };
+ static byte [] GetChunkSizeBytes (int size, bool final)
+ {
+ string str = String.Format ("{0:x}\r\n{1}", size, final ? "\r\n" : "");
+ return Encoding.ASCII.GetBytes (str);
+ }
+
+ internal void InternalWrite (byte [] buffer, int offset, int count)
+ {
+ if (ignore_errors) {
+ try {
+ stream.Write (buffer, offset, count);
+ } catch { }
+ } else {
+ stream.Write (buffer, offset, count);
+ }
+ }
+
+ public override void Write (byte [] buffer, int offset, int count)
+ {
+ if (disposed)
+ throw new ObjectDisposedException (GetType ().ToString ());
+
+ byte [] bytes = null;
+ MemoryStream ms = GetHeaders (false);
+ bool chunked = response.SendChunked;
+ if (ms != null) {
+ long start = ms.Position; // After the possible preamble for the encoding
+ ms.Position = ms.Length;
+ if (chunked) {
+ bytes = GetChunkSizeBytes (count, false);
+ ms.Write (bytes, 0, bytes.Length);
+ }
+
+ int new_count = Math.Min (count, 16384 - (int) ms.Position + (int) start);
+ ms.Write (buffer, offset, new_count);
+ count -= new_count;
+ offset += new_count;
+ InternalWrite (ms.GetBuffer (), (int) start, (int) (ms.Length - start));
+ ms.SetLength (0);
+ ms.Capacity = 0; // 'dispose' the buffer in ms.
+ } else if (chunked) {
+ bytes = GetChunkSizeBytes (count, false);
+ InternalWrite (bytes, 0, bytes.Length);
+ }
+
+ if (count > 0)
+ InternalWrite (buffer, offset, count);
+ if (chunked)
+ InternalWrite (crlf, 0, 2);
+ }
+
+ public override IAsyncResult BeginWrite (byte [] buffer, int offset, int count,
+ AsyncCallback cback, object state)
+ {
+ if (disposed)
+ throw new ObjectDisposedException (GetType ().ToString ());
+
+ byte [] bytes = null;
+ MemoryStream ms = GetHeaders (false);
+ bool chunked = response.SendChunked;
+ if (ms != null) {
+ long start = ms.Position;
+ ms.Position = ms.Length;
+ if (chunked) {
+ bytes = GetChunkSizeBytes (count, false);
+ ms.Write (bytes, 0, bytes.Length);
+ }
+ ms.Write (buffer, offset, count);
+ buffer = ms.GetBuffer ();
+ offset = (int) start;
+ count = (int) (ms.Position - start);
+ } else if (chunked) {
+ bytes = GetChunkSizeBytes (count, false);
+ InternalWrite (bytes, 0, bytes.Length);
+ }
+
+ return stream.BeginWrite (buffer, offset, count, cback, state);
+ }
+
+ public override void EndWrite (IAsyncResult ares)
+ {
+ if (disposed)
+ throw new ObjectDisposedException (GetType ().ToString ());
+
+ if (ignore_errors) {
+ try {
+ stream.EndWrite (ares);
+ if (response.SendChunked)
+ stream.Write (crlf, 0, 2);
+ } catch { }
+ } else {
+ stream.EndWrite (ares);
+ if (response.SendChunked)
+ stream.Write (crlf, 0, 2);
+ }
+ }
+
+ public override int Read ([In,Out] byte[] buffer, int offset, int count)
+ {
+ throw new NotSupportedException ();
+ }
+
+ public override IAsyncResult BeginRead (byte [] buffer, int offset, int count,
+ AsyncCallback cback, object state)
+ {
+ throw new NotSupportedException ();
+ }
+
+ public override int EndRead (IAsyncResult ares)
+ {
+ throw new NotSupportedException ();
+ }
+
+ public override long Seek (long offset, SeekOrigin origin)
+ {
+ throw new NotSupportedException ();
+ }
+
+ public override void SetLength (long value)
+ {
+ throw new NotSupportedException ();
+ }
+ }
+}
diff --git a/websocket-sharp/Net/WebHeaderCollection.cs b/websocket-sharp/Net/WebHeaderCollection.cs
new file mode 100644
index 00000000..7db31aa1
--- /dev/null
+++ b/websocket-sharp/Net/WebHeaderCollection.cs
@@ -0,0 +1,730 @@
+//
+// WebHeaderCollection.cs
+// Copied from System.Net.WebHeaderCollection
+//
+// Authors:
+// Lawrence Pit (loz@cable.a2000.nl)
+// Gonzalo Paniagua Javier (gonzalo@ximian.com)
+// Miguel de Icaza (miguel@novell.com)
+//
+// Copyright 2003 Ximian, Inc. (http://www.ximian.com)
+// Copyright 2007 Novell, Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Net;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+using System.Text;
+
+// See RFC 2068 par 4.2 Message Headers
+
+namespace WebSocketSharp.Net {
+
+ [Serializable]
+ [ComVisible(true)]
+ public class WebHeaderCollection : NameValueCollection, ISerializable
+ {
+ private static readonly Dictionary multiValue;
+ private static readonly Dictionary restricted;
+ private static readonly Dictionary restricted_response;
+
+ private bool internallyCreated = false;
+
+ // Static Initializer
+
+ static WebHeaderCollection ()
+ {
+ // the list of restricted header names as defined
+ // by the ms.net spec
+ restricted = new Dictionary (StringComparer.InvariantCultureIgnoreCase);
+ restricted.Add ("accept", true);
+ restricted.Add ("connection", true);
+ restricted.Add ("content-length", true);
+ restricted.Add ("content-type", true);
+ restricted.Add ("date", true);
+ restricted.Add ("expect", true);
+ restricted.Add ("host", true);
+ restricted.Add ("if-modified-since", true);
+ restricted.Add ("range", true);
+ restricted.Add ("referer", true);
+ restricted.Add ("transfer-encoding", true);
+ restricted.Add ("user-agent", true);
+ restricted.Add ("proxy-connection", true);
+
+ //
+ restricted_response = new Dictionary (StringComparer.InvariantCultureIgnoreCase);
+ restricted_response.Add ("Content-Length", true);
+ restricted_response.Add ("Transfer-Encoding", true);
+ restricted_response.Add ("WWW-Authenticate", true);
+
+ // see par 14 of RFC 2068 to see which header names
+ // accept multiple values each separated by a comma
+ multiValue = new Dictionary (StringComparer.InvariantCultureIgnoreCase);
+ multiValue.Add ("accept", true);
+ multiValue.Add ("accept-charset", true);
+ multiValue.Add ("accept-encoding", true);
+ multiValue.Add ("accept-language", true);
+ multiValue.Add ("accept-ranges", true);
+ multiValue.Add ("allow", true);
+ multiValue.Add ("authorization", true);
+ multiValue.Add ("cache-control", true);
+ multiValue.Add ("connection", true);
+ multiValue.Add ("content-encoding", true);
+ multiValue.Add ("content-language", true);
+ multiValue.Add ("expect", true);
+ multiValue.Add ("if-match", true);
+ multiValue.Add ("if-none-match", true);
+ multiValue.Add ("proxy-authenticate", true);
+ multiValue.Add ("public", true);
+ multiValue.Add ("range", true);
+ multiValue.Add ("transfer-encoding", true);
+ multiValue.Add ("upgrade", true);
+ multiValue.Add ("vary", true);
+ multiValue.Add ("via", true);
+ multiValue.Add ("warning", true);
+ multiValue.Add ("www-authenticate", true);
+
+ // Extra
+ multiValue.Add ("set-cookie", true);
+ multiValue.Add ("set-cookie2", true);
+ }
+
+ // Constructors
+
+ public WebHeaderCollection () { }
+
+ protected WebHeaderCollection (
+ SerializationInfo serializationInfo,
+ StreamingContext streamingContext)
+ {
+ int count;
+
+ try {
+ count = serializationInfo.GetInt32("Count");
+ for (int i = 0; i < count; i++)
+ this.Add (serializationInfo.GetString (i.ToString ()),
+ serializationInfo.GetString ((count + i).ToString ()));
+ } catch (SerializationException){
+ count = serializationInfo.GetInt32("count");
+ for (int i = 0; i < count; i++)
+ this.Add (serializationInfo.GetString ("k" + i),
+ serializationInfo.GetString ("v" + i));
+ }
+
+ }
+
+ internal WebHeaderCollection (bool internallyCreated)
+ {
+ this.internallyCreated = internallyCreated;
+ }
+
+ // Methods
+
+ public void Add (string header)
+ {
+ if (header == null)
+ throw new ArgumentNullException ("header");
+ int pos = header.IndexOf (':');
+ if (pos == -1)
+ throw new ArgumentException ("no colon found", "header");
+ this.Add (header.Substring (0, pos),
+ header.Substring (pos + 1));
+ }
+
+ public override void Add (string name, string value)
+ {
+ if (name == null)
+ throw new ArgumentNullException ("name");
+ if (internallyCreated && IsRestricted (name))
+ throw new ArgumentException ("This header must be modified with the appropiate property.");
+ this.AddWithoutValidate (name, value);
+ }
+
+ protected void AddWithoutValidate (string headerName, string headerValue)
+ {
+ if (!IsHeaderName (headerName))
+ throw new ArgumentException ("invalid header name: " + headerName, "headerName");
+ if (headerValue == null)
+ headerValue = String.Empty;
+ else
+ headerValue = headerValue.Trim ();
+ if (!IsHeaderValue (headerValue))
+ throw new ArgumentException ("invalid header value: " + headerValue, "headerValue");
+ base.Add (headerName, headerValue);
+ }
+
+ public override string [] GetValues (string header)
+ {
+ if (header == null)
+ throw new ArgumentNullException ("header");
+
+ string [] values = base.GetValues (header);
+ if (values == null || values.Length == 0)
+ return null;
+
+ /*
+ if (IsMultiValue (header)) {
+ values = GetMultipleValues (values);
+ }
+ */
+
+ return values;
+ }
+
+ public override string[] GetValues (int index)
+ {
+ string[] values = base.GetValues (index);
+ if (values == null || values.Length == 0) {
+ return(null);
+ }
+
+ return(values);
+ }
+
+ /* Now i wonder why this is here...
+ static string [] GetMultipleValues (string [] values)
+ {
+ ArrayList mvalues = new ArrayList (values.Length);
+ StringBuilder sb = null;
+ for (int i = 0; i < values.Length; ++i) {
+ string val = values [i];
+ if (val.IndexOf (',') == -1) {
+ mvalues.Add (val);
+ continue;
+ }
+
+ if (sb == null)
+ sb = new StringBuilder ();
+
+ bool quote = false;
+ for (int k = 0; k < val.Length; k++) {
+ char c = val [k];
+ if (c == '"') {
+ quote = !quote;
+ } else if (!quote && c == ',') {
+ mvalues.Add (sb.ToString ().Trim ());
+ sb.Length = 0;
+ continue;
+ }
+ sb.Append (c);
+ }
+
+ if (sb.Length > 0) {
+ mvalues.Add (sb.ToString ().Trim ());
+ sb.Length = 0;
+ }
+ }
+
+ return (string []) mvalues.ToArray (typeof (string));
+ }
+ */
+
+ public static bool IsRestricted (string headerName)
+ {
+ if (headerName == null)
+ throw new ArgumentNullException ("headerName");
+
+ if (headerName == "") // MS throw nullexception here!
+ throw new ArgumentException ("empty string", "headerName");
+
+ if (!IsHeaderName (headerName))
+ throw new ArgumentException ("Invalid character in header");
+
+ return restricted.ContainsKey (headerName);
+ }
+
+ public static bool IsRestricted (string headerName, bool response)
+ {
+ if (String.IsNullOrEmpty (headerName))
+ throw new ArgumentNullException ("headerName");
+
+ if (!IsHeaderName (headerName))
+ throw new ArgumentException ("Invalid character in header");
+
+
+ if (response)
+ return restricted_response.ContainsKey (headerName);
+ return restricted.ContainsKey (headerName);
+ }
+
+ public override void OnDeserialization (object sender)
+ {
+ }
+
+ public override void Remove (string name)
+ {
+ if (name == null)
+ throw new ArgumentNullException ("name");
+ if (internallyCreated && IsRestricted (name))
+ throw new ArgumentException ("restricted header");
+ base.Remove (name);
+ }
+
+ public override void Set (string name, string value)
+ {
+ if (name == null)
+ throw new ArgumentNullException ("name");
+ if (internallyCreated && IsRestricted (name))
+ throw new ArgumentException ("restricted header");
+ if (!IsHeaderName (name))
+ throw new ArgumentException ("invalid header name");
+ if (value == null)
+ value = String.Empty;
+ else
+ value = value.Trim ();
+ if (!IsHeaderValue (value))
+ throw new ArgumentException ("invalid header value");
+ base.Set (name, value);
+ }
+
+ public byte[] ToByteArray ()
+ {
+ return Encoding.UTF8.GetBytes(ToString ());
+ }
+
+ internal string ToStringMultiValue ()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ int count = base.Count;
+ for (int i = 0; i < count ; i++) {
+ string key = GetKey (i);
+ if (IsMultiValue (key)) {
+ foreach (string v in GetValues (i)) {
+ sb.Append (key)
+ .Append (": ")
+ .Append (v)
+ .Append ("\r\n");
+ }
+ } else {
+ sb.Append (key)
+ .Append (": ")
+ .Append (Get (i))
+ .Append ("\r\n");
+ }
+ }
+ return sb.Append("\r\n").ToString();
+ }
+
+ public override string ToString ()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ int count = base.Count;
+ for (int i = 0; i < count ; i++)
+ sb.Append (GetKey (i))
+ .Append (": ")
+ .Append (Get (i))
+ .Append ("\r\n");
+
+ return sb.Append("\r\n").ToString();
+ }
+
+ void ISerializable.GetObjectData (
+ SerializationInfo serializationInfo,
+ StreamingContext streamingContext)
+ {
+ GetObjectData (serializationInfo, streamingContext);
+ }
+
+ public override void GetObjectData (SerializationInfo serializationInfo, StreamingContext streamingContext)
+ {
+ int count = base.Count;
+ serializationInfo.AddValue ("Count", count);
+ for (int i = 0; i < count; i++) {
+ serializationInfo.AddValue (i.ToString (), GetKey (i));
+ serializationInfo.AddValue ((count + i).ToString (), Get (i));
+ }
+ }
+
+ public override string[] AllKeys
+ {
+ get {
+ return(base.AllKeys);
+ }
+ }
+
+ public override int Count
+ {
+ get {
+ return(base.Count);
+ }
+ }
+
+ public override KeysCollection Keys
+ {
+ get {
+ return(base.Keys);
+ }
+ }
+
+ public override string Get (int index)
+ {
+ return(base.Get (index));
+ }
+
+ public override string Get (string name)
+ {
+ return(base.Get (name));
+ }
+
+ public override string GetKey (int index)
+ {
+ return(base.GetKey (index));
+ }
+
+ public void Add (HttpRequestHeader header, string value)
+ {
+ Add (RequestHeaderToString (header), value);
+ }
+
+ public void Remove (HttpRequestHeader header)
+ {
+ Remove (RequestHeaderToString (header));
+ }
+
+ public void Set (HttpRequestHeader header, string value)
+ {
+ Set (RequestHeaderToString (header), value);
+ }
+
+ public void Add (HttpResponseHeader header, string value)
+ {
+ Add (ResponseHeaderToString (header), value);
+ }
+
+ public void Remove (HttpResponseHeader header)
+ {
+ Remove (ResponseHeaderToString (header));
+ }
+
+ public void Set (HttpResponseHeader header, string value)
+ {
+ Set (ResponseHeaderToString (header), value);
+ }
+
+ string RequestHeaderToString (HttpRequestHeader value)
+ {
+ switch (value){
+ case HttpRequestHeader.CacheControl:
+ return "Cache-Control";
+ case HttpRequestHeader.Connection:
+ return "Connection";
+ case HttpRequestHeader.Date:
+ return "Date";
+ case HttpRequestHeader.KeepAlive:
+ return "Keep-Alive";
+ case HttpRequestHeader.Pragma:
+ return "Pragma";
+ case HttpRequestHeader.Trailer:
+ return "Trailer";
+ case HttpRequestHeader.TransferEncoding:
+ return "Transfer-Encoding";
+ case HttpRequestHeader.Upgrade:
+ return "Upgrade";
+ case HttpRequestHeader.Via:
+ return "Via";
+ case HttpRequestHeader.Warning:
+ return "Warning";
+ case HttpRequestHeader.Allow:
+ return "Allow";
+ case HttpRequestHeader.ContentLength:
+ return "Content-Length";
+ case HttpRequestHeader.ContentType:
+ return "Content-Type";
+ case HttpRequestHeader.ContentEncoding:
+ return "Content-Encoding";
+ case HttpRequestHeader.ContentLanguage:
+ return "Content-Language";
+ case HttpRequestHeader.ContentLocation:
+ return "Content-Location";
+ case HttpRequestHeader.ContentMd5:
+ return "Content-MD5";
+ case HttpRequestHeader.ContentRange:
+ return "Content-Range";
+ case HttpRequestHeader.Expires:
+ return "Expires";
+ case HttpRequestHeader.LastModified:
+ return "Last-Modified";
+ case HttpRequestHeader.Accept:
+ return "Accept";
+ case HttpRequestHeader.AcceptCharset:
+ return "Accept-Charset";
+ case HttpRequestHeader.AcceptEncoding:
+ return "Accept-Encoding";
+ case HttpRequestHeader.AcceptLanguage:
+ return "accept-language";
+ case HttpRequestHeader.Authorization:
+ return "Authorization";
+ case HttpRequestHeader.Cookie:
+ return "Cookie";
+ case HttpRequestHeader.Expect:
+ return "Expect";
+ case HttpRequestHeader.From:
+ return "From";
+ case HttpRequestHeader.Host:
+ return "Host";
+ case HttpRequestHeader.IfMatch:
+ return "If-Match";
+ case HttpRequestHeader.IfModifiedSince:
+ return "If-Modified-Since";
+ case HttpRequestHeader.IfNoneMatch:
+ return "If-None-Match";
+ case HttpRequestHeader.IfRange:
+ return "If-Range";
+ case HttpRequestHeader.IfUnmodifiedSince:
+ return "If-Unmodified-Since";
+ case HttpRequestHeader.MaxForwards:
+ return "Max-Forwards";
+ case HttpRequestHeader.ProxyAuthorization:
+ return "Proxy-Authorization";
+ case HttpRequestHeader.Referer:
+ return "Referer";
+ case HttpRequestHeader.Range:
+ return "Range";
+ case HttpRequestHeader.Te:
+ return "TE";
+ case HttpRequestHeader.Translate:
+ return "Translate";
+ case HttpRequestHeader.UserAgent:
+ return "User-Agent";
+ default:
+ throw new InvalidOperationException ();
+ }
+ }
+
+
+ public string this[HttpRequestHeader hrh]
+ {
+ get {
+ return Get (RequestHeaderToString (hrh));
+ }
+
+ set {
+ Add (RequestHeaderToString (hrh), value);
+ }
+ }
+
+ string ResponseHeaderToString (HttpResponseHeader value)
+ {
+ switch (value){
+ case HttpResponseHeader.CacheControl:
+ return "Cache-Control";
+ case HttpResponseHeader.Connection:
+ return "Connection";
+ case HttpResponseHeader.Date:
+ return "Date";
+ case HttpResponseHeader.KeepAlive:
+ return "Keep-Alive";
+ case HttpResponseHeader.Pragma:
+ return "Pragma";
+ case HttpResponseHeader.Trailer:
+ return "Trailer";
+ case HttpResponseHeader.TransferEncoding:
+ return "Transfer-Encoding";
+ case HttpResponseHeader.Upgrade:
+ return "Upgrade";
+ case HttpResponseHeader.Via:
+ return "Via";
+ case HttpResponseHeader.Warning:
+ return "Warning";
+ case HttpResponseHeader.Allow:
+ return "Allow";
+ case HttpResponseHeader.ContentLength:
+ return "Content-Length";
+ case HttpResponseHeader.ContentType:
+ return "Content-Type";
+ case HttpResponseHeader.ContentEncoding:
+ return "Content-Encoding";
+ case HttpResponseHeader.ContentLanguage:
+ return "Content-Language";
+ case HttpResponseHeader.ContentLocation:
+ return "Content-Location";
+ case HttpResponseHeader.ContentMd5:
+ return "Content-MD5";
+ case HttpResponseHeader.ContentRange:
+ return "Content-Range";
+ case HttpResponseHeader.Expires:
+ return "Expires";
+ case HttpResponseHeader.LastModified:
+ return "Last-Modified";
+ case HttpResponseHeader.AcceptRanges:
+ return "Accept-Ranges";
+ case HttpResponseHeader.Age:
+ return "Age";
+ case HttpResponseHeader.ETag:
+ return "ETag";
+ case HttpResponseHeader.Location:
+ return "Location";
+ case HttpResponseHeader.ProxyAuthenticate:
+ return "Proxy-Authenticate";
+ case HttpResponseHeader.RetryAfter:
+ return "Retry-After";
+ case HttpResponseHeader.Server:
+ return "Server";
+ case HttpResponseHeader.SetCookie:
+ return "Set-Cookie";
+ case HttpResponseHeader.Vary:
+ return "Vary";
+ case HttpResponseHeader.WwwAuthenticate:
+ return "WWW-Authenticate";
+ default:
+ throw new InvalidOperationException ();
+ }
+ }
+
+ public string this[HttpResponseHeader hrh]
+ {
+ get
+ {
+ return Get (ResponseHeaderToString (hrh));
+ }
+
+ set
+ {
+ Add (ResponseHeaderToString (hrh), value);
+ }
+ }
+
+ public override void Clear ()
+ {
+ base.Clear ();
+ }
+
+ public override IEnumerator GetEnumerator ()
+ {
+ return(base.GetEnumerator ());
+ }
+
+ // Internal Methods
+
+ // With this we don't check for invalid characters in header. See bug #55994.
+ internal void SetInternal (string header)
+ {
+ int pos = header.IndexOf (':');
+ if (pos == -1)
+ throw new ArgumentException ("no colon found", "header");
+
+ SetInternal (header.Substring (0, pos), header.Substring (pos + 1));
+ }
+
+ internal void SetInternal (string name, string value)
+ {
+ if (value == null)
+ value = String.Empty;
+ else
+ value = value.Trim ();
+ if (!IsHeaderValue (value))
+ throw new ArgumentException ("invalid header value");
+
+ if (IsMultiValue (name)) {
+ base.Add (name, value);
+ } else {
+ base.Remove (name);
+ base.Set (name, value);
+ }
+ }
+
+ internal void RemoveAndAdd (string name, string value)
+ {
+ if (value == null)
+ value = String.Empty;
+ else
+ value = value.Trim ();
+
+ base.Remove (name);
+ base.Set (name, value);
+ }
+
+ internal void RemoveInternal (string name)
+ {
+ if (name == null)
+ throw new ArgumentNullException ("name");
+ base.Remove (name);
+ }
+
+ // Private Methods
+
+ internal static bool IsMultiValue (string headerName)
+ {
+ if (headerName == null || headerName == "")
+ return false;
+
+ return multiValue.ContainsKey (headerName);
+ }
+
+ internal static bool IsHeaderValue (string value)
+ {
+ // TEXT any 8 bit value except CTL's (0-31 and 127)
+ // but including \r\n space and \t
+ // after a newline at least one space or \t must follow
+ // certain header fields allow comments ()
+
+ int len = value.Length;
+ for (int i = 0; i < len; i++) {
+ char c = value [i];
+ if (c == 127)
+ return false;
+ if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t'))
+ return false;
+ if (c == '\n' && ++i < len) {
+ c = value [i];
+ if (c != ' ' && c != '\t')
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ internal static bool IsHeaderName (string name)
+ {
+ if (name == null || name.Length == 0)
+ return false;
+
+ int len = name.Length;
+ for (int i = 0; i < len; i++) {
+ char c = name [i];
+ if (c > 126 || !allowed_chars [(int) c])
+ return false;
+ }
+
+ return true;
+ }
+
+ static bool [] allowed_chars = new bool [126] {
+ false, false, false, false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, true, false, true, true, true, true, false, false, false, true,
+ true, false, true, true, false, true, true, true, true, true, true, true, true, true, true, false,
+ false, false, false, false, false, false, true, true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
+ false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
+ false, true, false
+ };
+ }
+}
diff --git a/websocket-sharp/Net/WebSocketContext.cs b/websocket-sharp/Net/WebSocketContext.cs
new file mode 100644
index 00000000..55f86480
--- /dev/null
+++ b/websocket-sharp/Net/WebSocketContext.cs
@@ -0,0 +1,55 @@
+#region MIT License
+/**
+ * WebSocketContext.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012 sta.blockhead
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Security.Principal;
+
+namespace WebSocketSharp.Net {
+
+ public abstract class WebSocketContext {
+
+ protected WebSocketContext()
+ {
+ }
+
+ public abstract CookieCollection CookieCollection { get; }
+ public abstract NameValueCollection Headers { get; }
+ public abstract bool IsAuthenticated { get; }
+ public abstract bool IsSecureConnection { get; }
+ public abstract bool IsLocal { get; }
+ public abstract string Origin { get; }
+ public abstract Uri RequestUri { get; }
+ public abstract string SecWebSocketKey { get; }
+ public abstract IEnumerable SecWebSocketProtocols { get; }
+ public abstract string SecWebSocketVersion { get; }
+ public abstract IPrincipal User { get; }
+ public abstract WebSocket WebSocket { get; }
+ }
+}
diff --git a/websocket-sharp/RequestHandshake.cs b/websocket-sharp/RequestHandshake.cs
index 47ca9e7c..912e7a3b 100644
--- a/websocket-sharp/RequestHandshake.cs
+++ b/websocket-sharp/RequestHandshake.cs
@@ -27,36 +27,47 @@
#endregion
using System;
-using System.Net;
using System.Collections.Specialized;
using System.Text;
+using WebSocketSharp.Net;
namespace WebSocketSharp {
public class RequestHandshake : Handshake
{
+ #region Private Constructor
+
private RequestHandshake()
{
}
- public RequestHandshake(string uri)
+ #endregion
+
+
+ #region Public Constructor
+
+ public RequestHandshake(string uriString)
{
- Method = "GET";
- Uri = uri;
- Version = "HTTP/1.1";
- Headers = new NameValueCollection();
+ HttpMethod = "GET";
+ RequestUri = uriString.ToUri();
AddHeader("Upgrade", "websocket");
AddHeader("Connection", "Upgrade");
}
+ #endregion
+
+ #region Properties
+
+ public string HttpMethod { get; private set; }
+
public bool IsWebSocketRequest {
get {
- if (Method != "GET")
+ if (HttpMethod != "GET")
return false;
- if (Version != "HTTP/1.1")
+ if (ProtocolVersion != HttpVersion.Version11)
return false;
if (!HeaderExists("Upgrade", "websocket"))
@@ -78,8 +89,21 @@ namespace WebSocketSharp {
}
}
- public string Method { get; private set; }
- public string Uri { get; private set; }
+ public Uri RequestUri { get; private set; }
+
+ #endregion
+
+ #region Public Static Methods
+
+ public static RequestHandshake Parse(WebSocketContext context)
+ {
+ return new RequestHandshake {
+ Headers = context.Headers,
+ HttpMethod = "GET",
+ RequestUri = context.RequestUri,
+ ProtocolVersion = HttpVersion.Version11
+ };
+ }
public static RequestHandshake Parse(string[] request)
{
@@ -92,18 +116,22 @@ namespace WebSocketSharp {
headers.Add(request[i]);
return new RequestHandshake {
- Headers = headers,
- Method = requestLine[0],
- Uri = requestLine[1],
- Version = requestLine[2]
+ Headers = headers,
+ HttpMethod = requestLine[0],
+ RequestUri = requestLine[1].ToUri(),
+ ProtocolVersion = new Version(requestLine[2].Substring(5))
};
}
+ #endregion
+
+ #region Public Method
+
public override string ToString()
{
var buffer = new StringBuilder();
- buffer.AppendFormat("{0} {1} {2}{3}", Method, Uri, Version, _crlf);
+ buffer.AppendFormat("{0} {1} HTTP/{2}{3}", HttpMethod, RequestUri, ProtocolVersion, _crlf);
foreach (string key in Headers.AllKeys)
buffer.AppendFormat("{0}: {1}{2}", key, Headers[key], _crlf);
@@ -112,5 +140,7 @@ namespace WebSocketSharp {
return buffer.ToString();
}
+
+ #endregion
}
}
diff --git a/websocket-sharp/ResponseHandshake.cs b/websocket-sharp/ResponseHandshake.cs
index bda69941..41356219 100644
--- a/websocket-sharp/ResponseHandshake.cs
+++ b/websocket-sharp/ResponseHandshake.cs
@@ -28,31 +28,35 @@
using System;
using System.Collections.Specialized;
-using System.Net;
using System.Text;
+using WebSocketSharp.Net;
namespace WebSocketSharp {
public class ResponseHandshake : Handshake
{
+ #region Public Constructor
+
public ResponseHandshake()
{
- Version = "HTTP/1.1";
- Status = "101";
- Reason = "Switching Protocols";
- Headers = new NameValueCollection();
+ StatusCode = "101";
+ Reason = "Switching Protocols";
AddHeader("Upgrade", "websocket");
AddHeader("Connection", "Upgrade");
}
+ #endregion
+
+ #region Properties
+
public bool IsWebSocketResponse {
get {
- if (Version != "HTTP/1.1")
+ if (ProtocolVersion != HttpVersion.Version11)
return false;
- if (Status != "101")
+ if (StatusCode != "101")
return false;
if (!HeaderExists("Upgrade", "websocket"))
@@ -68,8 +72,12 @@ namespace WebSocketSharp {
}
}
- public string Reason { get; private set; }
- public string Status { get; private set; }
+ public string Reason { get; private set; }
+ public string StatusCode { get; private set; }
+
+ #endregion
+
+ #region Public Static Methods
public static ResponseHandshake Parse(string[] response)
{
@@ -86,18 +94,22 @@ namespace WebSocketSharp {
headers.Add(response[i]);
return new ResponseHandshake {
- Headers = headers,
- Reason = reason.ToString(),
- Status = statusLine[1],
- Version = statusLine[0]
+ Headers = headers,
+ Reason = reason.ToString(),
+ StatusCode = statusLine[1],
+ ProtocolVersion = new Version(statusLine[0].Substring(5))
};
}
+ #endregion
+
+ #region Public Methods
+
public override string ToString()
{
var buffer = new StringBuilder();
- buffer.AppendFormat("{0} {1} {2}{3}", Version, Status, Reason, _crlf);
+ buffer.AppendFormat("HTTP/{0} {1} {2}{3}", ProtocolVersion, StatusCode, Reason, _crlf);
foreach (string key in Headers.AllKeys)
buffer.AppendFormat("{0}: {1}{2}", key, Headers[key], _crlf);
@@ -106,5 +118,7 @@ namespace WebSocketSharp {
return buffer.ToString();
}
+
+ #endregion
}
}
diff --git a/websocket-sharp/Server/HttpServer.cs b/websocket-sharp/Server/HttpServer.cs
new file mode 100644
index 00000000..1d834801
--- /dev/null
+++ b/websocket-sharp/Server/HttpServer.cs
@@ -0,0 +1,187 @@
+#region MIT License
+/**
+ * HttpServer.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012 sta.blockhead
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#endregion
+
+using System;
+using System.Configuration;
+using System.IO;
+using System.Threading;
+using WebSocketSharp.Net;
+
+namespace WebSocketSharp.Server {
+
+ public class HttpServer
+ where T : WebSocketService, new()
+ {
+ #region Fields
+
+ private Thread _acceptRequestThread;
+ private HttpListener _listener;
+ private int _port;
+ private string _rootPath;
+ private Uri _wsPath;
+ private WebSocketServer _wsServer;
+
+ #endregion
+
+ #region Constructors
+
+ public HttpServer()
+ : this(80)
+ {
+ }
+
+ public HttpServer(int port)
+ : this(port, "/")
+ {
+ }
+
+ public HttpServer(int port, string wsPath)
+ {
+ _listener = new HttpListener();
+ _port = port;
+ var prefix = String.Format(
+ "http{0}://*:{1}/", _port == 443 ? "s" : String.Empty, _port);
+ _listener.Prefixes.Add(prefix);
+ _rootPath = ConfigurationManager.AppSettings["RootPath"];
+ _wsPath = wsPath.ToUri();
+ _wsServer = new WebSocketServer();
+ }
+
+ #endregion
+
+ #region Property
+
+ public int Port {
+ get { return _port; }
+ }
+
+ #endregion
+
+ #region Events
+
+ public event EventHandler OnError;
+ public event EventHandler OnResponse;
+
+ #endregion
+
+ #region Private Methods
+
+ private void acceptRequest()
+ {
+ while (true)
+ {
+ try
+ {
+ var context = _listener.GetContext();
+ respond(context);
+ }
+ catch (HttpListenerException)
+ {
+ // HttpListener has been closed.
+ break;
+ }
+ catch (Exception ex)
+ {
+ OnError.Emit(this, new ErrorEventArgs(ex.Message));
+ break;
+ }
+ }
+ }
+
+ private void respond(HttpListenerContext context)
+ {
+ WaitCallback respondCb = (state) =>
+ {
+ try
+ {
+ var req = context.Request;
+ var res = context.Response;
+
+ if (req.IsWebSocketRequest)
+ {
+ upgradeToWebSocket(context);
+ }
+ else
+ {
+ OnResponse.Emit(this, new ResponseEventArgs(context));
+ res.Close();
+ }
+ }
+ catch (Exception ex)
+ {
+ OnError.Emit(this, new ErrorEventArgs(ex.Message));
+ }
+ };
+ ThreadPool.QueueUserWorkItem(respondCb);
+ }
+
+ private void startAcceptRequestThread()
+ {
+ _acceptRequestThread = new Thread(new ThreadStart(acceptRequest));
+ _acceptRequestThread.IsBackground = true;
+ _acceptRequestThread.Start();
+ }
+
+ private void upgradeToWebSocket(HttpListenerContext context)
+ {
+ var wsContext = context.AcceptWebSocket();
+ var socket = wsContext.WebSocket;
+ if (_wsPath.ToString() != "/")
+ socket.Url = _wsPath;
+ _wsServer.BindWebSocket(socket);
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public byte[] GetFile(string path)
+ {
+ var filePath = _rootPath + path;
+ if (File.Exists(filePath))
+ return File.ReadAllBytes(filePath);
+
+ return null;
+ }
+
+ public void Start()
+ {
+ _listener.Start();
+ startAcceptRequestThread();
+ }
+
+ public void Stop()
+ {
+ _listener.Close();
+ _acceptRequestThread.Join(5 * 1000);
+ _wsServer.StopServices();
+ }
+
+ #endregion
+ }
+}
diff --git a/websocket-sharp/Server/ResponseEventArgs.cs b/websocket-sharp/Server/ResponseEventArgs.cs
new file mode 100644
index 00000000..2dbe7fa1
--- /dev/null
+++ b/websocket-sharp/Server/ResponseEventArgs.cs
@@ -0,0 +1,43 @@
+#region MIT License
+/**
+ * ResponseEventArgs.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012 sta.blockhead
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#endregion
+
+using System;
+using WebSocketSharp.Net;
+
+namespace WebSocketSharp.Server {
+
+ public class ResponseEventArgs : EventArgs
+ {
+ public HttpListenerContext Context { get; private set; }
+
+ public ResponseEventArgs(HttpListenerContext context)
+ {
+ Context = context;
+ }
+ }
+}
diff --git a/websocket-sharp/Server/WebSocketServer.cs b/websocket-sharp/Server/WebSocketServer.cs
index c55a4d95..a3b9e7b8 100644
--- a/websocket-sharp/Server/WebSocketServer.cs
+++ b/websocket-sharp/Server/WebSocketServer.cs
@@ -42,16 +42,27 @@ namespace WebSocketSharp.Server {
public class WebSocketServer
where T : WebSocketService, new()
{
- #region Private Fields
+ #region Fields
private Thread _acceptClientThread;
+ private bool _isSelfHost;
private Dictionary _services;
private TcpListener _tcpListener;
private Uri _uri;
#endregion
- #region Constructors
+ #region Internal Constructor
+
+ internal WebSocketServer()
+ {
+ _services = new Dictionary();
+ _isSelfHost = false;
+ }
+
+ #endregion
+
+ #region Public Constructors
public WebSocketServer(string url)
{
@@ -81,6 +92,7 @@ namespace WebSocketSharp.Server {
_tcpListener = new TcpListener(ips[0], port);
_services = new Dictionary();
+ _isSelfHost = true;
}
public WebSocketServer(int port)
@@ -97,6 +109,7 @@ namespace WebSocketSharp.Server {
_tcpListener = new TcpListener(IPAddress.Any, port);
_services = new Dictionary();
+ _isSelfHost = true;
}
#endregion
@@ -113,6 +126,10 @@ namespace WebSocketSharp.Server {
get { return (IPEndPoint)_tcpListener.LocalEndpoint; }
}
+ public bool IsSelfHost {
+ get { return _isSelfHost; }
+ }
+
public int Port
{
get { return Endpoint.Port; }
@@ -209,14 +226,21 @@ namespace WebSocketSharp.Server {
public void Start()
{
+ if (!_isSelfHost)
+ return;
+
_tcpListener.Start();
startAcceptClientThread();
}
public void Stop()
{
- _tcpListener.Stop();
- _acceptClientThread.Join(5 * 1000);
+ if (_isSelfHost)
+ {
+ _tcpListener.Stop();
+ _acceptClientThread.Join(5 * 1000);
+ }
+
StopServices();
}
diff --git a/websocket-sharp/WebSocket.cs b/websocket-sharp/WebSocket.cs
index 74df5b35..8e581edf 100644
--- a/websocket-sharp/WebSocket.cs
+++ b/websocket-sharp/WebSocket.cs
@@ -46,6 +46,7 @@ using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using WebSocketSharp.Frame;
+using WebSocketSharp.Net;
namespace WebSocketSharp
{
@@ -62,6 +63,7 @@ namespace WebSocketSharp
private string _base64key;
private string _binaryType;
+ private HttpListenerWebSocketContext _context;
private IPEndPoint _endPoint;
private AutoResetEvent _exitedMessageLoop;
private string _extensions;
@@ -103,6 +105,15 @@ namespace WebSocketSharp
#region Internal Constructor
+ internal WebSocket(HttpListenerWebSocketContext context)
+ : this()
+ {
+ _uri = new Uri("/", UriKind.Relative);
+ _context = context;
+ _isClient = false;
+ _isSecure = _context.IsSecureConnection;
+ }
+
internal WebSocket(Uri uri, TcpClient tcpClient)
: this()
{
@@ -219,8 +230,14 @@ namespace WebSocketSharp
get { return _unTransmittedBuffer; }
}
- public string Url {
- get { return _uri.ToString(); }
+ public Uri Url {
+ get { return _uri; }
+ set {
+ if (_readyState != WsState.CONNECTING || _isClient)
+ return;
+
+ _uri = value;
+ }
}
#endregion
@@ -260,7 +277,7 @@ namespace WebSocketSharp
private void close(PayloadData data)
{
#if DEBUG
- Console.WriteLine("WS: Info@close: Current thread IsBackground?: {0}", Thread.CurrentThread.IsBackground);
+ Console.WriteLine("WS: Info@close: Current thread IsBackground ?: {0}", Thread.CurrentThread.IsBackground);
#endif
lock(_forClose)
{
@@ -278,7 +295,7 @@ namespace WebSocketSharp
ReadyState = WsState.CLOSING;
}
-
+
OnClose.Emit(this, new CloseEventArgs(data));
var frame = createFrame(Fin.FINAL, Opcode.CLOSE, data);
closeHandshake(frame);
@@ -311,12 +328,20 @@ namespace WebSocketSharp
private void closeConnection()
{
+ if (_context != null)
+ {
+ _context.BaseContext.Response.Close();
+ _wsStream = null;
+ _context = null;
+ }
+
if (_wsStream != null)
{
_wsStream.Dispose();
_wsStream = null;
}
- else if (_netStream != null)
+
+ if (_netStream != null)
{
_netStream.Dispose();
_netStream = null;
@@ -441,6 +466,31 @@ namespace WebSocketSharp
}
private void createServerStream()
+ {
+ if (_context != null)
+ {
+ _wsStream = createServerStreamFromContext();
+ return;
+ }
+
+ if (_tcpClient != null)
+ {
+ _wsStream = createServerStreamFromTcpClient();
+ return;
+ }
+ }
+
+ private IWsStream createServerStreamFromContext()
+ {
+ var stream = _context.BaseContext.Connection.Stream;
+
+ if (IsSecure)
+ return new WsStream((SslStream)stream);
+
+ return new WsStream((NetworkStream)stream);
+ }
+
+ private IWsStream createServerStreamFromTcpClient()
{
_netStream = _tcpClient.GetStream();
@@ -448,15 +498,13 @@ namespace WebSocketSharp
{
_sslStream = new SslStream(_netStream);
- string certPath = ConfigurationManager.AppSettings["ServerCertPath"];
+ var certPath = ConfigurationManager.AppSettings["ServerCertPath"];
_sslStream.AuthenticateAsServer(new X509Certificate(certPath));
- _wsStream = new WsStream(_sslStream);
-
- return;
+ return new WsStream(_sslStream);
}
- _wsStream = new WsStream(_netStream);
+ return new WsStream(_netStream);
}
private void doHandshake()
@@ -492,7 +540,7 @@ namespace WebSocketSharp
{
if (!request.IsWebSocketRequest)
{
- message = "Not WebSocket request.";
+ message = "Invalid WebSocket request.";
return false;
}
@@ -504,18 +552,12 @@ namespace WebSocketSharp
};
};
+ if (!isValidRequestUri(request.RequestUri, func("Request URI"), out message))
+ return false;
+
if (_uri.IsAbsoluteUri)
- {
- if (_uri.PathAndQuery.NotEqualsDo(request.Uri, func("Request URI"), out message, false))
- return false;
-
if (!isValidRequestHost(request.GetHeaderValues("Host")[0], func("Host"), out message))
return false;
- }
-
- if (!_uri.IsAbsoluteUri)
- if (_uri.ToString().NotEqualsDo(request.Uri, func("Request URI"), out message, false))
- return false;
if (!request.HeaderExists("Sec-WebSocket-Version", _version))
{
@@ -556,11 +598,33 @@ namespace WebSocketSharp
return true;
}
+ private bool isValidRequestUri(Uri requestUri, Func func, out string message)
+ {
+ if (_uri.IsAbsoluteUri && requestUri.IsAbsoluteUri)
+ if (_uri.ToString().NotEqualsDo(requestUri.ToString(), func, out message, false))
+ return false;
+
+ if (_uri.IsAbsoluteUri && !requestUri.IsAbsoluteUri)
+ if (_uri.PathAndQuery.NotEqualsDo(requestUri.ToString(), func, out message, false))
+ return false;
+
+ if (!_uri.IsAbsoluteUri && requestUri.IsAbsoluteUri)
+ if (_uri.ToString().NotEqualsDo(requestUri.PathAndQuery, func, out message, false))
+ return false;
+
+ if (!_uri.IsAbsoluteUri && !requestUri.IsAbsoluteUri)
+ if (_uri.ToString().NotEqualsDo(requestUri.ToString(), func, out message, false))
+ return false;
+
+ message = String.Empty;
+ return true;
+ }
+
private bool isValidResponse(ResponseHandshake response, out string message)
{
if (!response.IsWebSocketResponse)
{
- message = "Not WebSocket response.";
+ message = "Invalid WebSocket response.";
return false;
}
@@ -719,7 +783,7 @@ namespace WebSocketSharp
else
{
#if DEBUG
- Console.WriteLine("WS: Info@receive: Client starts closing handshake.");
+ Console.WriteLine("WS: Info@receive: Start closing handshake.");
#endif
close(CloseStatusCode.INCORRECT_DATA, String.Empty);
return null;
@@ -733,7 +797,7 @@ namespace WebSocketSharp
else if (frame.Opcode == Opcode.CLOSE)
{// FINAL & CLOSE
#if DEBUG
- Console.WriteLine("WS: Info@receive: Server starts closing handshake.");
+ Console.WriteLine("WS: Info@receive: Start closing handshake.");
#endif
close(frame.PayloadData);
return null;
@@ -753,7 +817,7 @@ namespace WebSocketSharp
else
{// FINAL & (TEXT | BINARY)
#if DEBUG
- Console.WriteLine("WS: Info@receive: Client starts closing handshake.");
+ Console.WriteLine("WS: Info@receive: Start closing handshake.");
#endif
close(CloseStatusCode.INCORRECT_DATA, String.Empty);
return null;
@@ -773,7 +837,7 @@ namespace WebSocketSharp
goto default;
case Opcode.CLOSE:
#if DEBUG
- Console.WriteLine("WS: Info@receive: Server starts closing handshake.");
+ Console.WriteLine("WS: Info@receive: Start closing handshake.");
#endif
close(payloadData);
break;
@@ -796,6 +860,9 @@ namespace WebSocketSharp
private RequestHandshake receiveOpeningHandshake()
{
+ if (_context != null)
+ return RequestHandshake.Parse(_context);
+
return RequestHandshake.Parse(readHandshake());
}
diff --git a/websocket-sharp/bin/Debug/websocket-sharp.dll b/websocket-sharp/bin/Debug/websocket-sharp.dll
index 0901b46b..3950c3fe 100755
Binary files a/websocket-sharp/bin/Debug/websocket-sharp.dll and b/websocket-sharp/bin/Debug/websocket-sharp.dll differ
diff --git a/websocket-sharp/bin/Debug/websocket-sharp.dll.mdb b/websocket-sharp/bin/Debug/websocket-sharp.dll.mdb
index 615a3a20..f3c03973 100644
Binary files a/websocket-sharp/bin/Debug/websocket-sharp.dll.mdb and b/websocket-sharp/bin/Debug/websocket-sharp.dll.mdb differ
diff --git a/websocket-sharp/bin/Debug_Ubuntu/websocket-sharp.dll b/websocket-sharp/bin/Debug_Ubuntu/websocket-sharp.dll
index 764c252c..b2c76b4d 100755
Binary files a/websocket-sharp/bin/Debug_Ubuntu/websocket-sharp.dll and b/websocket-sharp/bin/Debug_Ubuntu/websocket-sharp.dll differ
diff --git a/websocket-sharp/bin/Debug_Ubuntu/websocket-sharp.dll.mdb b/websocket-sharp/bin/Debug_Ubuntu/websocket-sharp.dll.mdb
index f63a5858..86949826 100644
Binary files a/websocket-sharp/bin/Debug_Ubuntu/websocket-sharp.dll.mdb and b/websocket-sharp/bin/Debug_Ubuntu/websocket-sharp.dll.mdb differ
diff --git a/websocket-sharp/bin/Release/websocket-sharp.dll b/websocket-sharp/bin/Release/websocket-sharp.dll
index cae2760e..d33d5220 100755
Binary files a/websocket-sharp/bin/Release/websocket-sharp.dll and b/websocket-sharp/bin/Release/websocket-sharp.dll differ
diff --git a/websocket-sharp/bin/Release_Ubuntu/websocket-sharp.dll b/websocket-sharp/bin/Release_Ubuntu/websocket-sharp.dll
index d8d1e088..4160d674 100755
Binary files a/websocket-sharp/bin/Release_Ubuntu/websocket-sharp.dll and b/websocket-sharp/bin/Release_Ubuntu/websocket-sharp.dll differ
diff --git a/websocket-sharp/websocket-sharp.csproj b/websocket-sharp/websocket-sharp.csproj
index 1d6f607e..26785bd7 100644
--- a/websocket-sharp/websocket-sharp.csproj
+++ b/websocket-sharp/websocket-sharp.csproj
@@ -79,10 +79,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/websocket-sharp/websocket-sharp.pidb b/websocket-sharp/websocket-sharp.pidb
index 7f9fb8fb..22781a1e 100644
Binary files a/websocket-sharp/websocket-sharp.pidb and b/websocket-sharp/websocket-sharp.pidb differ