From 8b6a06e90f3f19b80d2fb1139c0578e139b35419 Mon Sep 17 00:00:00 2001
From: KINDNICK <68893236+KINDNICK@users.noreply.github.com>
Date: Mon, 7 Jul 2025 04:26:38 +0900
Subject: [PATCH] =?UTF-8?q?ADD=20:=20=EC=8A=A4=ED=8A=B8=EB=A6=BC=EB=8D=B1?=
=?UTF-8?q?=20=ED=94=8C=EB=9F=AC=EA=B7=B8=EC=9D=B8=20=EC=B6=94=EA=B0=80=20?=
=?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EC=95=84=EC=9D=B4?=
=?UTF-8?q?=ED=85=9C=20+=20=EC=B9=B4=EB=A9=94=EB=9D=BC=20=EA=B8=B0?=
=?UTF-8?q?=EB=8A=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Assets/External/websocket-sharp.meta | 8 +
.../External/websocket-sharp/AssemblyInfo.cs | 26 +
.../websocket-sharp/AssemblyInfo.cs.meta | 2 +
Assets/External/websocket-sharp/ByteOrder.cs | 47 +
.../websocket-sharp/ByteOrder.cs.meta | 2 +
.../websocket-sharp/CloseEventArgs.cs | 118 +
.../websocket-sharp/CloseEventArgs.cs.meta | 2 +
.../websocket-sharp/CloseStatusCode.cs | 120 +
.../websocket-sharp/CloseStatusCode.cs.meta | 2 +
.../websocket-sharp/CompressionMethod.cs | 52 +
.../websocket-sharp/CompressionMethod.cs.meta | 2 +
.../websocket-sharp/ErrorEventArgs.cs | 115 +
.../websocket-sharp/ErrorEventArgs.cs.meta | 2 +
Assets/External/websocket-sharp/Ext.cs | 1902 ++++++++
Assets/External/websocket-sharp/Ext.cs.meta | 2 +
Assets/External/websocket-sharp/Fin.cs | 52 +
Assets/External/websocket-sharp/Fin.cs.meta | 2 +
Assets/External/websocket-sharp/HttpBase.cs | 317 ++
.../External/websocket-sharp/HttpBase.cs.meta | 2 +
.../External/websocket-sharp/HttpRequest.cs | 253 +
.../websocket-sharp/HttpRequest.cs.meta | 2 +
.../External/websocket-sharp/HttpResponse.cs | 274 ++
.../websocket-sharp/HttpResponse.cs.meta | 2 +
Assets/External/websocket-sharp/LogData.cs | 159 +
.../External/websocket-sharp/LogData.cs.meta | 2 +
Assets/External/websocket-sharp/LogLevel.cs | 67 +
.../External/websocket-sharp/LogLevel.cs.meta | 2 +
Assets/External/websocket-sharp/Logger.cs | 345 ++
.../External/websocket-sharp/Logger.cs.meta | 2 +
Assets/External/websocket-sharp/Mask.cs | 52 +
Assets/External/websocket-sharp/Mask.cs.meta | 2 +
.../websocket-sharp/MessageEventArgs.cs | 192 +
.../websocket-sharp/MessageEventArgs.cs.meta | 2 +
Assets/External/websocket-sharp/Net.meta | 8 +
.../Net/AuthenticationChallenge.cs | 280 ++
.../Net/AuthenticationChallenge.cs.meta | 2 +
.../Net/AuthenticationResponse.cs | 464 ++
.../Net/AuthenticationResponse.cs.meta | 2 +
.../Net/AuthenticationSchemes.cs | 66 +
.../Net/AuthenticationSchemes.cs.meta | 2 +
Assets/External/websocket-sharp/Net/Chunk.cs | 93 +
.../websocket-sharp/Net/Chunk.cs.meta | 2 +
.../websocket-sharp/Net/ChunkStream.cs | 429 ++
.../websocket-sharp/Net/ChunkStream.cs.meta | 2 +
.../Net/ChunkedRequestStream.cs | 283 ++
.../Net/ChunkedRequestStream.cs.meta | 2 +
.../Net/ClientSslConfiguration.cs | 311 ++
.../Net/ClientSslConfiguration.cs.meta | 2 +
Assets/External/websocket-sharp/Net/Cookie.cs | 1032 ++++
.../websocket-sharp/Net/Cookie.cs.meta | 2 +
.../websocket-sharp/Net/CookieCollection.cs | 882 ++++
.../Net/CookieCollection.cs.meta | 2 +
.../websocket-sharp/Net/CookieException.cs | 169 +
.../Net/CookieException.cs.meta | 2 +
.../websocket-sharp/Net/EndPointListener.cs | 604 +++
.../Net/EndPointListener.cs.meta | 2 +
.../websocket-sharp/Net/EndPointManager.cs | 261 +
.../Net/EndPointManager.cs.meta | 2 +
.../websocket-sharp/Net/HttpBasicIdentity.cs | 82 +
.../Net/HttpBasicIdentity.cs.meta | 2 +
.../websocket-sharp/Net/HttpConnection.cs | 653 +++
.../Net/HttpConnection.cs.meta | 2 +
.../websocket-sharp/Net/HttpDigestIdentity.cs | 192 +
.../Net/HttpDigestIdentity.cs.meta | 2 +
.../websocket-sharp/Net/HttpHeaderInfo.cs | 128 +
.../Net/HttpHeaderInfo.cs.meta | 2 +
.../websocket-sharp/Net/HttpHeaderType.cs | 44 +
.../Net/HttpHeaderType.cs.meta | 2 +
.../websocket-sharp/Net/HttpListener.cs | 1028 ++++
.../websocket-sharp/Net/HttpListener.cs.meta | 2 +
.../Net/HttpListenerAsyncResult.cs | 202 +
.../Net/HttpListenerAsyncResult.cs.meta | 2 +
.../Net/HttpListenerContext.cs | 452 ++
.../Net/HttpListenerContext.cs.meta | 2 +
.../Net/HttpListenerException.cs | 140 +
.../Net/HttpListenerException.cs.meta | 2 +
.../websocket-sharp/Net/HttpListenerPrefix.cs | 243 +
.../Net/HttpListenerPrefix.cs.meta | 2 +
.../Net/HttpListenerPrefixCollection.cs | 295 ++
.../Net/HttpListenerPrefixCollection.cs.meta | 2 +
.../Net/HttpListenerRequest.cs | 943 ++++
.../Net/HttpListenerRequest.cs.meta | 2 +
.../Net/HttpListenerResponse.cs | 1201 +++++
.../Net/HttpListenerResponse.cs.meta | 2 +
.../websocket-sharp/Net/HttpRequestHeader.cs | 233 +
.../Net/HttpRequestHeader.cs.meta | 2 +
.../websocket-sharp/Net/HttpResponseHeader.cs | 189 +
.../Net/HttpResponseHeader.cs.meta | 2 +
.../websocket-sharp/Net/HttpStatusCode.cs | 346 ++
.../Net/HttpStatusCode.cs.meta | 2 +
.../Net/HttpStreamAsyncResult.cs | 201 +
.../Net/HttpStreamAsyncResult.cs.meta | 2 +
.../websocket-sharp/Net/HttpUtility.cs | 1237 +++++
.../websocket-sharp/Net/HttpUtility.cs.meta | 2 +
.../websocket-sharp/Net/HttpVersion.cs | 73 +
.../websocket-sharp/Net/HttpVersion.cs.meta | 2 +
.../websocket-sharp/Net/InputChunkState.cs | 52 +
.../Net/InputChunkState.cs.meta | 2 +
.../websocket-sharp/Net/InputState.cs | 49 +
.../websocket-sharp/Net/InputState.cs.meta | 2 +
.../External/websocket-sharp/Net/LineState.cs | 50 +
.../websocket-sharp/Net/LineState.cs.meta | 2 +
.../websocket-sharp/Net/NetworkCredential.cs | 217 +
.../Net/NetworkCredential.cs.meta | 2 +
.../Net/QueryStringCollection.cs | 144 +
.../Net/QueryStringCollection.cs.meta | 2 +
.../websocket-sharp/Net/ReadBufferState.cs | 129 +
.../Net/ReadBufferState.cs.meta | 2 +
.../websocket-sharp/Net/RequestStream.cs | 354 ++
.../websocket-sharp/Net/RequestStream.cs.meta | 2 +
.../websocket-sharp/Net/ResponseStream.cs | 416 ++
.../Net/ResponseStream.cs.meta | 2 +
.../Net/ServerSslConfiguration.cs | 239 +
.../Net/ServerSslConfiguration.cs.meta | 2 +
.../Net/WebHeaderCollection.cs | 1907 ++++++++
.../Net/WebHeaderCollection.cs.meta | 2 +
.../websocket-sharp/Net/WebSockets.meta | 8 +
.../HttpListenerWebSocketContext.cs | 406 ++
.../HttpListenerWebSocketContext.cs.meta | 2 +
.../WebSockets/TcpListenerWebSocketContext.cs | 501 ++
.../TcpListenerWebSocketContext.cs.meta | 2 +
.../Net/WebSockets/WebSocketContext.cs | 224 +
.../Net/WebSockets/WebSocketContext.cs.meta | 2 +
Assets/External/websocket-sharp/Opcode.cs | 68 +
.../External/websocket-sharp/Opcode.cs.meta | 2 +
.../External/websocket-sharp/PayloadData.cs | 212 +
.../websocket-sharp/PayloadData.cs.meta | 2 +
Assets/External/websocket-sharp/Rsv.cs | 53 +
Assets/External/websocket-sharp/Rsv.cs.meta | 2 +
Assets/External/websocket-sharp/Server.meta | 8 +
.../Server/HttpRequestEventArgs.cs | 266 +
.../Server/HttpRequestEventArgs.cs.meta | 2 +
.../websocket-sharp/Server/HttpServer.cs | 1334 +++++
.../websocket-sharp/Server/HttpServer.cs.meta | 2 +
.../Server/IWebSocketSession.cs | 67 +
.../Server/IWebSocketSession.cs.meta | 2 +
.../websocket-sharp/Server/ServerState.cs | 40 +
.../Server/ServerState.cs.meta | 2 +
.../Server/WebSocketBehavior.cs | 1570 ++++++
.../Server/WebSocketBehavior.cs.meta | 2 +
.../websocket-sharp/Server/WebSocketServer.cs | 1185 +++++
.../Server/WebSocketServer.cs.meta | 2 +
.../Server/WebSocketServiceHost.cs | 227 +
.../Server/WebSocketServiceHost.cs.meta | 2 +
.../Server/WebSocketServiceHost`1.cs | 95 +
.../Server/WebSocketServiceHost`1.cs.meta | 2 +
.../Server/WebSocketServiceManager.cs | 614 +++
.../Server/WebSocketServiceManager.cs.meta | 2 +
.../Server/WebSocketSessionManager.cs | 1641 +++++++
.../Server/WebSocketSessionManager.cs.meta | 2 +
Assets/External/websocket-sharp/WebSocket.cs | 4320 +++++++++++++++++
.../websocket-sharp/WebSocket.cs.meta | 2 +
.../websocket-sharp/WebSocketException.cs | 129 +
.../WebSocketException.cs.meta | 2 +
.../websocket-sharp/WebSocketFrame.cs | 916 ++++
.../websocket-sharp/WebSocketFrame.cs.meta | 2 +
.../websocket-sharp/WebSocketState.cs | 64 +
.../websocket-sharp/WebSocketState.cs.meta | 2 +
Assets/External/websocket-sharp/doc.meta | 8 +
.../External/websocket-sharp/doc/.gitignore | 4 +
Assets/External/websocket-sharp/doc/doc.sh | 31 +
.../External/websocket-sharp/doc/doc.sh.meta | 7 +
.../websocket-sharp.csproj.meta | 7 +
.../websocket-sharp/websocket-sharp.snk | Bin 0 -> 596 bytes
.../websocket-sharp/websocket-sharp.snk.meta | 7 +
.../250506_페닷/250506_페닷_아바타.unity | 4 +-
Assets/Scripts/Streamdeck/DebugTest.cs | 9 -
Assets/Scripts/Streamdeck/DebugTest.cs.meta | 2 -
.../Scripts/Streamdeck/SimpleEventExample.cs | 101 -
.../Streamdeck/SimpleEventExample.cs.meta | 2 -
.../SimpleStreamDockCommunicator.cs | 274 --
.../SimpleStreamDockCommunicator.cs.meta | 2 -
.../Streamdeck/StreamDeckServerManager.cs | 647 +++
.../StreamDeckServerManager.cs.meta | 2 +
Assets/Scripts/Streamingle/IController.cs | 12 +
.../Scripts/Streamingle/IController.cs.meta | 2 +
.../Controllers/CameraController.cs | 160 +-
.../Controllers/Editor.meta | 8 +
.../Editor/ItemControllerEditor.cs | 259 +
.../Editor/ItemControllerEditor.cs.meta | 2 +
.../Controllers/ItemController.cs | 562 +++
.../Controllers/ItemController.cs.meta | 2 +
Assets/Scripts/Vrmtool.meta | 8 +
Assets/Scripts/Vrmtool/ColliderAutoSetup.cs | 69 +
.../Scripts/Vrmtool/ColliderAutoSetup.cs.meta | 11 +
.../Vrmtool/ColliderMoveEditorWindow.cs | 38 +
.../Vrmtool/ColliderMoveEditorWindow.cs.meta | 11 +
Assets/Scripts/Vrmtool/ColliderMoveTool.cs | 47 +
.../Scripts/Vrmtool/ColliderMoveTool.cs.meta | 11 +
.../Vrmtool/RemoveInvalidVRMSpringBones.cs | 50 +
.../RemoveInvalidVRMSpringBones.cs.meta | 11 +
.../Scripts/Vrmtool/VRMSpringBoneMoveTool.cs | 234 +
.../Vrmtool/VRMSpringBoneMoveTool.cs.meta | 11 +
Assets/Scripts/Vrmtool/VRMToolsWindow.cs | 626 +++
Assets/Scripts/Vrmtool/VRMToolsWindow.cs.meta | 11 +
Assets/test.meta | 8 +
Assets/test/GlobalVolumeProfile.asset | 3 +
Assets/test/GlobalVolumeProfile.asset.meta | 8 +
Packages/manifest.json | 4 +-
Packages/packages-lock.json | 4 +-
StreamDock-Plugin-SDK/Icon/unity.png | 3 -
StreamDock-Plugin-SDK/README_한국어.md | 3 -
.../package.json | 3 -
.../unity-communication-plugin/manifest.json | 3 -
.../unity-communication-plugin/package.json | 3 -
.../unity-communication-plugin/plugin/app.exe | Bin 37650776 -> 0 bytes
.../plugin/index.js | 172 -
.../plugin/package.json | 3 -
.../propertyInspector/action1/index.html | 118 -
.../propertyInspector/action1/index.js | 14 -
.../propertyInspector/action2/index.html | 152 -
.../unity-communication-plugin/readme.md | 3 -
.../static/@unity.png | 3 -
.../unity-communication-plugin/static/CH.png | 3 -
.../static/default.jpg | 3 -
.../unity-communication-plugin/zh_CN.json | 3 -
.../Unity_Scripts/SimpleEventExample.cs | 101 -
.../SimpleStreamDockCommunicator.cs | 282 --
.../Unity_StreamDock_통신_가이드.md | 3 -
.../.gitignore | 3 +
.../README.md | 3 +
.../ar.json | 3 +
.../de.json | 3 +
.../en.json | 3 +
.../es.json | 3 +
.../fr.json | 3 +
.../it.json | 3 +
.../ja.json | 3 +
.../ko.json | 3 +
.../manifest.json | 3 +
.../pl.json | 3 +
.../plugin/autofile.js | 18 +-
.../plugin/enum.js | 133 +
.../plugin/index.html | 19 +
.../plugin/index.js | 322 ++
.../plugin/package.json | 3 +
.../plugin/utils/weatherService.js | 206 +
.../propertyInspector/action1/index.html | 106 +
.../propertyInspector/action1/index.js | 96 +
.../pt.json | 3 +
.../ru.json | 3 +
.../static}/action.js | 41 +-
.../static/css/caret.svg | 0
.../static/css/check.png | 0
.../static/css/check.svg | 0
.../static/css/elg_calendar.svg | 0
.../static/css/elg_calendar_inv.svg | 0
.../static/css/g_d8d8d8.svg | 0
.../static/css/rcheck.svg | 0
.../static/css/sdpi.css | 0
.../static/img/Luxury/100.png | 3 +
.../static/img/Luxury/101.png | 3 +
.../static/img/Luxury/104.png | 3 +
.../static/img/Luxury/150.png | 3 +
.../static/img/Luxury/151.png | 3 +
.../static/img/Luxury/300.png | 3 +
.../static/img/Luxury/302.png | 3 +
.../static/img/Luxury/304.png | 3 +
.../static/img/Luxury/305.png | 3 +
.../static/img/Luxury/306.png | 3 +
.../static/img/Luxury/307.png | 3 +
.../static/img/Luxury/310.png | 3 +
.../static/img/Luxury/311.png | 3 +
.../static/img/Luxury/312.png | 3 +
.../static/img/Luxury/313.png | 3 +
.../static/img/Luxury/350.png | 3 +
.../static/img/Luxury/400.png | 3 +
.../static/img/Luxury/401.png | 3 +
.../static/img/Luxury/402.png | 3 +
.../static/img/Luxury/403.png | 3 +
.../static/img/Luxury/404.png | 3 +
.../static/img/Luxury/407.png | 3 +
.../static/img/Luxury/457.png | 3 +
.../static/img/Luxury/500.png | 3 +
.../static/img/Luxury/502.png | 3 +
.../static/img/Luxury/503.png | 3 +
.../static/img/Luxury/508.png | 3 +
.../static/img/Modern/100-fill.svg | 18 +
.../static/img/Modern/101-fill.svg | 23 +
.../static/img/Modern/102-fill.svg | 29 +
.../static/img/Modern/103-fill.svg | 31 +
.../static/img/Modern/104-fill.svg | 40 +
.../static/img/Modern/150-fill.svg | 10 +
.../static/img/Modern/151-fill.svg | 16 +
.../static/img/Modern/152-fill.svg | 17 +
.../static/img/Modern/153-fill.svg | 23 +
.../static/img/Modern/154-fill.svg | 23 +
.../static/img/Modern/300-fill.svg | 25 +
.../static/img/Modern/301-fill.svg | 28 +
.../static/img/Modern/302-fill.svg | 16 +
.../static/img/Modern/303-fill.svg | 19 +
.../static/img/Modern/304-fill.svg | 17 +
.../static/img/Modern/305-fill.svg | 13 +
.../static/img/Modern/306-fill.svg | 16 +
.../static/img/Modern/307-fill.svg | 16 +
.../static/img/Modern/308-fill.svg | 21 +
.../static/img/Modern/309-fill.svg | 17 +
.../static/img/Modern/310-fill.svg | 19 +
.../static/img/Modern/311-fill.svg | 21 +
.../static/img/Modern/312-fill.svg | 23 +
.../static/img/Modern/313-fill.svg | 16 +
.../static/img/Modern/314-fill.svg | 23 +
.../static/img/Modern/315-fill.svg | 27 +
.../static/img/Modern/316-fill.svg | 31 +
.../static/img/Modern/317-fill.svg | 19 +
.../static/img/Modern/318-fill.svg | 21 +
.../static/img/Modern/350-fill.svg | 19 +
.../static/img/Modern/351-fill.svg | 22 +
.../static/img/Modern/399-fill.svg | 12 +
.../static/img/Modern/400-fill.svg | 19 +
.../static/img/Modern/401-fill.svg | 23 +
.../static/img/Modern/402-fill.svg | 27 +
.../static/img/Modern/403-fill.svg | 31 +
.../static/img/Modern/404-fill.svg | 22 +
.../static/img/Modern/405-fill.svg | 22 +
.../static/img/Modern/406-fill.svg | 28 +
.../static/img/Modern/407-fill.svg | 30 +
.../static/img/Modern/408-fill.svg | 25 +
.../static/img/Modern/409-fill.svg | 32 +
.../static/img/Modern/410-fill.svg | 39 +
.../static/img/Modern/456-fill.svg | 23 +
.../static/img/Modern/457-fill.svg | 25 +
.../static/img/Modern/499-fill.svg | 21 +
.../static/img/Modern/500-fill.svg | 13 +
.../static/img/Modern/501-fill.svg | 15 +
.../static/img/Modern/502-fill.svg | 10 +
.../static/img/Modern/503-fill.svg | 19 +
.../static/img/Modern/504-fill.svg | 16 +
.../static/img/Modern/507-fill.svg | 16 +
.../static/img/Modern/508-fill.svg | 17 +
.../static/img/Modern/509-fill.svg | 18 +
.../static/img/Modern/510-fill.svg | 20 +
.../static/img/Modern/511-fill.svg | 12 +
.../static/img/Modern/512-fill.svg | 13 +
.../static/img/Modern/513-fill.svg | 14 +
.../static/img/Modern/514-fill.svg | 17 +
.../static/img/Modern/515-fill.svg | 24 +
.../static/img/Modern/900-fill.svg | 14 +
.../static/img/Modern/901-fill.svg | 16 +
.../static/img/Modern/999-fill.svg | 15 +
.../static/img/cate.png | 3 +
.../static/img/default.jpg | 3 +
.../static/img/tm.png | 3 +
.../static/plugin.js | 151 +
.../static/utils/axios.js | 1 +
.../static/utils/color.js | 78 +
.../static/utils/common.js | 84 +
.../static/utils/worker.js | 5 +
.../zh_CN.json | 3 +
StreamDock-Plugin-SDK/개발환경_설정_가이드.md | 3 -
.../샘플_플러그인_개발_가이드.md | 3 -
Streamdeck/Implementation_Guide.md | 3 +
Streamdeck/JSON_Protocol_Specification.md | 3 +
Streamdeck/Streamingle_Plugin_Architecture.md | 3 +
.../README.md | 3 +
.../com.mirabox.streamingle.sdPlugin/build.js | 288 ++
.../com.mirabox.streamingle.sdPlugin/debug.js | 133 +
.../com.mirabox.streamingle.sdPlugin/debug.js | 133 +
.../images/action.png | 3 +
.../manifest.json | 3 +
.../plugin/index.html | 314 ++
.../plugin/index.js | 664 +++
.../plugin/log/streamingle_plugin | 0
.../plugin/utils/plugin.js | 143 +-
.../propertyInspector/camera/index.html | 187 +
.../propertyInspector/camera/index.js | 778 +++
.../propertyInspector/utils/action.js | 157 +
.../propertyInspector/utils/axios.js | 1 +
.../utils/bootstrap-icons.css | 1556 ++++++
.../propertyInspector/utils/bootstrap.min.css | 7 +
.../propertyInspector/utils/common.js | 0
.../utils/fonts/bootstrap-icons.woff | Bin 0 -> 123384 bytes
.../utils/fonts/bootstrap-icons.woff2 | Bin 0 -> 92064 bytes
.../streamingle_plugin | 9 +
.../com.mirabox.streamingle.sdPlugin/test.js | 65 +
.../com.mirabox.streamingle.sdPlugin/debug.js | 133 +
.../images/action.png | 3 +
.../manifest.json | 3 +
.../plugin/index.html | 314 ++
.../plugin/index.js | 228 +
.../plugin/log/streamingle_plugin | 0
.../plugin/utils/plugin.js | 223 +
.../propertyInspector/camera/index.html | 187 +
.../propertyInspector/camera/index.js | 778 +++
.../propertyInspector/utils/action.js | 157 +
.../propertyInspector/utils/axios.js | 1 +
.../utils/bootstrap-icons.css | 1556 ++++++
.../propertyInspector/utils/bootstrap.min.css | 7 +
.../propertyInspector/utils/common.js | 81 +
.../utils/fonts/bootstrap-icons.woff | Bin 0 -> 123384 bytes
.../utils/fonts/bootstrap-icons.woff2 | Bin 0 -> 92064 bytes
.../streamingle_plugin | 9 +
.../com.mirabox.streamingle.sdPlugin/test.js | 65 +
.../images/camera_icon.png | 3 +
.../images/item_icon.png | 3 +
.../images/item_icon_inactive.png | 3 +
.../images/plugin_logo.png | 3 +
.../manifest.json | 3 +
.../node_modules/.package-lock.json | 3 +
.../node_modules/ws/LICENSE | 20 +
.../node_modules/ws/README.md | 3 +
.../node_modules/ws/browser.js | 8 +
.../node_modules/ws/index.js | 13 +
.../node_modules/ws/lib/buffer-util.js | 131 +
.../node_modules/ws/lib/constants.js | 18 +
.../node_modules/ws/lib/event-target.js | 292 ++
.../node_modules/ws/lib/extension.js | 203 +
.../node_modules/ws/lib/limiter.js | 55 +
.../node_modules/ws/lib/permessage-deflate.js | 528 ++
.../node_modules/ws/lib/receiver.js | 706 +++
.../node_modules/ws/lib/sender.js | 602 +++
.../node_modules/ws/lib/stream.js | 161 +
.../node_modules/ws/lib/subprotocol.js | 62 +
.../node_modules/ws/lib/validation.js | 152 +
.../node_modules/ws/lib/websocket-server.js | 550 +++
.../node_modules/ws/lib/websocket.js | 1388 ++++++
.../node_modules/ws/package.json | 3 +
.../node_modules/ws/wrapper.mjs | 8 +
.../package-lock.json | 3 +
.../package.json | 3 +
.../plugin/index.html | 10 +
.../plugin/index.js | 825 ++++
.../propertyInspector/camera/index.html | 187 +
.../propertyInspector/camera/index.js | 407 ++
.../propertyInspector/item/index.html | 187 +
.../propertyInspector/item/index.js | 294 ++
.../propertyInspector/utils/action.js | 157 +
.../propertyInspector/utils/axios.js | 1 +
.../utils/bootstrap-icons.css | 1556 ++++++
.../propertyInspector/utils/bootstrap.min.css | 7 +
.../propertyInspector/utils/common.js | 81 +
.../utils/fonts/bootstrap-icons.woff | Bin 0 -> 123384 bytes
.../utils/fonts/bootstrap-icons.woff2 | Bin 0 -> 92064 bytes
.../streamingle_plugin | 9 +
.../com.mirabox.streamingle.sdPlugin/test.js | 65 +
435 files changed, 56906 insertions(+), 1392 deletions(-)
create mode 100644 Assets/External/websocket-sharp.meta
create mode 100644 Assets/External/websocket-sharp/AssemblyInfo.cs
create mode 100644 Assets/External/websocket-sharp/AssemblyInfo.cs.meta
create mode 100644 Assets/External/websocket-sharp/ByteOrder.cs
create mode 100644 Assets/External/websocket-sharp/ByteOrder.cs.meta
create mode 100644 Assets/External/websocket-sharp/CloseEventArgs.cs
create mode 100644 Assets/External/websocket-sharp/CloseEventArgs.cs.meta
create mode 100644 Assets/External/websocket-sharp/CloseStatusCode.cs
create mode 100644 Assets/External/websocket-sharp/CloseStatusCode.cs.meta
create mode 100644 Assets/External/websocket-sharp/CompressionMethod.cs
create mode 100644 Assets/External/websocket-sharp/CompressionMethod.cs.meta
create mode 100644 Assets/External/websocket-sharp/ErrorEventArgs.cs
create mode 100644 Assets/External/websocket-sharp/ErrorEventArgs.cs.meta
create mode 100644 Assets/External/websocket-sharp/Ext.cs
create mode 100644 Assets/External/websocket-sharp/Ext.cs.meta
create mode 100644 Assets/External/websocket-sharp/Fin.cs
create mode 100644 Assets/External/websocket-sharp/Fin.cs.meta
create mode 100644 Assets/External/websocket-sharp/HttpBase.cs
create mode 100644 Assets/External/websocket-sharp/HttpBase.cs.meta
create mode 100644 Assets/External/websocket-sharp/HttpRequest.cs
create mode 100644 Assets/External/websocket-sharp/HttpRequest.cs.meta
create mode 100644 Assets/External/websocket-sharp/HttpResponse.cs
create mode 100644 Assets/External/websocket-sharp/HttpResponse.cs.meta
create mode 100644 Assets/External/websocket-sharp/LogData.cs
create mode 100644 Assets/External/websocket-sharp/LogData.cs.meta
create mode 100644 Assets/External/websocket-sharp/LogLevel.cs
create mode 100644 Assets/External/websocket-sharp/LogLevel.cs.meta
create mode 100644 Assets/External/websocket-sharp/Logger.cs
create mode 100644 Assets/External/websocket-sharp/Logger.cs.meta
create mode 100644 Assets/External/websocket-sharp/Mask.cs
create mode 100644 Assets/External/websocket-sharp/Mask.cs.meta
create mode 100644 Assets/External/websocket-sharp/MessageEventArgs.cs
create mode 100644 Assets/External/websocket-sharp/MessageEventArgs.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net.meta
create mode 100644 Assets/External/websocket-sharp/Net/AuthenticationChallenge.cs
create mode 100644 Assets/External/websocket-sharp/Net/AuthenticationChallenge.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/AuthenticationResponse.cs
create mode 100644 Assets/External/websocket-sharp/Net/AuthenticationResponse.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/AuthenticationSchemes.cs
create mode 100644 Assets/External/websocket-sharp/Net/AuthenticationSchemes.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/Chunk.cs
create mode 100644 Assets/External/websocket-sharp/Net/Chunk.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/ChunkStream.cs
create mode 100644 Assets/External/websocket-sharp/Net/ChunkStream.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/ChunkedRequestStream.cs
create mode 100644 Assets/External/websocket-sharp/Net/ChunkedRequestStream.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/ClientSslConfiguration.cs
create mode 100644 Assets/External/websocket-sharp/Net/ClientSslConfiguration.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/Cookie.cs
create mode 100644 Assets/External/websocket-sharp/Net/Cookie.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/CookieCollection.cs
create mode 100644 Assets/External/websocket-sharp/Net/CookieCollection.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/CookieException.cs
create mode 100644 Assets/External/websocket-sharp/Net/CookieException.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/EndPointListener.cs
create mode 100644 Assets/External/websocket-sharp/Net/EndPointListener.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/EndPointManager.cs
create mode 100644 Assets/External/websocket-sharp/Net/EndPointManager.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/HttpBasicIdentity.cs
create mode 100644 Assets/External/websocket-sharp/Net/HttpBasicIdentity.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/HttpConnection.cs
create mode 100644 Assets/External/websocket-sharp/Net/HttpConnection.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/HttpDigestIdentity.cs
create mode 100644 Assets/External/websocket-sharp/Net/HttpDigestIdentity.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/HttpHeaderInfo.cs
create mode 100644 Assets/External/websocket-sharp/Net/HttpHeaderInfo.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/HttpHeaderType.cs
create mode 100644 Assets/External/websocket-sharp/Net/HttpHeaderType.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/HttpListener.cs
create mode 100644 Assets/External/websocket-sharp/Net/HttpListener.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/HttpListenerAsyncResult.cs
create mode 100644 Assets/External/websocket-sharp/Net/HttpListenerAsyncResult.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/HttpListenerContext.cs
create mode 100644 Assets/External/websocket-sharp/Net/HttpListenerContext.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/HttpListenerException.cs
create mode 100644 Assets/External/websocket-sharp/Net/HttpListenerException.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/HttpListenerPrefix.cs
create mode 100644 Assets/External/websocket-sharp/Net/HttpListenerPrefix.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/HttpListenerPrefixCollection.cs
create mode 100644 Assets/External/websocket-sharp/Net/HttpListenerPrefixCollection.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/HttpListenerRequest.cs
create mode 100644 Assets/External/websocket-sharp/Net/HttpListenerRequest.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/HttpListenerResponse.cs
create mode 100644 Assets/External/websocket-sharp/Net/HttpListenerResponse.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/HttpRequestHeader.cs
create mode 100644 Assets/External/websocket-sharp/Net/HttpRequestHeader.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/HttpResponseHeader.cs
create mode 100644 Assets/External/websocket-sharp/Net/HttpResponseHeader.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/HttpStatusCode.cs
create mode 100644 Assets/External/websocket-sharp/Net/HttpStatusCode.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/HttpStreamAsyncResult.cs
create mode 100644 Assets/External/websocket-sharp/Net/HttpStreamAsyncResult.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/HttpUtility.cs
create mode 100644 Assets/External/websocket-sharp/Net/HttpUtility.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/HttpVersion.cs
create mode 100644 Assets/External/websocket-sharp/Net/HttpVersion.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/InputChunkState.cs
create mode 100644 Assets/External/websocket-sharp/Net/InputChunkState.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/InputState.cs
create mode 100644 Assets/External/websocket-sharp/Net/InputState.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/LineState.cs
create mode 100644 Assets/External/websocket-sharp/Net/LineState.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/NetworkCredential.cs
create mode 100644 Assets/External/websocket-sharp/Net/NetworkCredential.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/QueryStringCollection.cs
create mode 100644 Assets/External/websocket-sharp/Net/QueryStringCollection.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/ReadBufferState.cs
create mode 100644 Assets/External/websocket-sharp/Net/ReadBufferState.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/RequestStream.cs
create mode 100644 Assets/External/websocket-sharp/Net/RequestStream.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/ResponseStream.cs
create mode 100644 Assets/External/websocket-sharp/Net/ResponseStream.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/ServerSslConfiguration.cs
create mode 100644 Assets/External/websocket-sharp/Net/ServerSslConfiguration.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/WebHeaderCollection.cs
create mode 100644 Assets/External/websocket-sharp/Net/WebHeaderCollection.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/WebSockets.meta
create mode 100644 Assets/External/websocket-sharp/Net/WebSockets/HttpListenerWebSocketContext.cs
create mode 100644 Assets/External/websocket-sharp/Net/WebSockets/HttpListenerWebSocketContext.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs
create mode 100644 Assets/External/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs.meta
create mode 100644 Assets/External/websocket-sharp/Net/WebSockets/WebSocketContext.cs
create mode 100644 Assets/External/websocket-sharp/Net/WebSockets/WebSocketContext.cs.meta
create mode 100644 Assets/External/websocket-sharp/Opcode.cs
create mode 100644 Assets/External/websocket-sharp/Opcode.cs.meta
create mode 100644 Assets/External/websocket-sharp/PayloadData.cs
create mode 100644 Assets/External/websocket-sharp/PayloadData.cs.meta
create mode 100644 Assets/External/websocket-sharp/Rsv.cs
create mode 100644 Assets/External/websocket-sharp/Rsv.cs.meta
create mode 100644 Assets/External/websocket-sharp/Server.meta
create mode 100644 Assets/External/websocket-sharp/Server/HttpRequestEventArgs.cs
create mode 100644 Assets/External/websocket-sharp/Server/HttpRequestEventArgs.cs.meta
create mode 100644 Assets/External/websocket-sharp/Server/HttpServer.cs
create mode 100644 Assets/External/websocket-sharp/Server/HttpServer.cs.meta
create mode 100644 Assets/External/websocket-sharp/Server/IWebSocketSession.cs
create mode 100644 Assets/External/websocket-sharp/Server/IWebSocketSession.cs.meta
create mode 100644 Assets/External/websocket-sharp/Server/ServerState.cs
create mode 100644 Assets/External/websocket-sharp/Server/ServerState.cs.meta
create mode 100644 Assets/External/websocket-sharp/Server/WebSocketBehavior.cs
create mode 100644 Assets/External/websocket-sharp/Server/WebSocketBehavior.cs.meta
create mode 100644 Assets/External/websocket-sharp/Server/WebSocketServer.cs
create mode 100644 Assets/External/websocket-sharp/Server/WebSocketServer.cs.meta
create mode 100644 Assets/External/websocket-sharp/Server/WebSocketServiceHost.cs
create mode 100644 Assets/External/websocket-sharp/Server/WebSocketServiceHost.cs.meta
create mode 100644 Assets/External/websocket-sharp/Server/WebSocketServiceHost`1.cs
create mode 100644 Assets/External/websocket-sharp/Server/WebSocketServiceHost`1.cs.meta
create mode 100644 Assets/External/websocket-sharp/Server/WebSocketServiceManager.cs
create mode 100644 Assets/External/websocket-sharp/Server/WebSocketServiceManager.cs.meta
create mode 100644 Assets/External/websocket-sharp/Server/WebSocketSessionManager.cs
create mode 100644 Assets/External/websocket-sharp/Server/WebSocketSessionManager.cs.meta
create mode 100644 Assets/External/websocket-sharp/WebSocket.cs
create mode 100644 Assets/External/websocket-sharp/WebSocket.cs.meta
create mode 100644 Assets/External/websocket-sharp/WebSocketException.cs
create mode 100644 Assets/External/websocket-sharp/WebSocketException.cs.meta
create mode 100644 Assets/External/websocket-sharp/WebSocketFrame.cs
create mode 100644 Assets/External/websocket-sharp/WebSocketFrame.cs.meta
create mode 100644 Assets/External/websocket-sharp/WebSocketState.cs
create mode 100644 Assets/External/websocket-sharp/WebSocketState.cs.meta
create mode 100644 Assets/External/websocket-sharp/doc.meta
create mode 100644 Assets/External/websocket-sharp/doc/.gitignore
create mode 100644 Assets/External/websocket-sharp/doc/doc.sh
create mode 100644 Assets/External/websocket-sharp/doc/doc.sh.meta
create mode 100644 Assets/External/websocket-sharp/websocket-sharp.csproj.meta
create mode 100644 Assets/External/websocket-sharp/websocket-sharp.snk
create mode 100644 Assets/External/websocket-sharp/websocket-sharp.snk.meta
delete mode 100644 Assets/Scripts/Streamdeck/DebugTest.cs
delete mode 100644 Assets/Scripts/Streamdeck/DebugTest.cs.meta
delete mode 100644 Assets/Scripts/Streamdeck/SimpleEventExample.cs
delete mode 100644 Assets/Scripts/Streamdeck/SimpleEventExample.cs.meta
delete mode 100644 Assets/Scripts/Streamdeck/SimpleStreamDockCommunicator.cs
delete mode 100644 Assets/Scripts/Streamdeck/SimpleStreamDockCommunicator.cs.meta
create mode 100644 Assets/Scripts/Streamdeck/StreamDeckServerManager.cs
create mode 100644 Assets/Scripts/Streamdeck/StreamDeckServerManager.cs.meta
create mode 100644 Assets/Scripts/Streamingle/IController.cs
create mode 100644 Assets/Scripts/Streamingle/IController.cs.meta
create mode 100644 Assets/Scripts/Streamingle/StreamingleControl/Controllers/Editor.meta
create mode 100644 Assets/Scripts/Streamingle/StreamingleControl/Controllers/Editor/ItemControllerEditor.cs
create mode 100644 Assets/Scripts/Streamingle/StreamingleControl/Controllers/Editor/ItemControllerEditor.cs.meta
create mode 100644 Assets/Scripts/Streamingle/StreamingleControl/Controllers/ItemController.cs
create mode 100644 Assets/Scripts/Streamingle/StreamingleControl/Controllers/ItemController.cs.meta
create mode 100644 Assets/Scripts/Vrmtool.meta
create mode 100644 Assets/Scripts/Vrmtool/ColliderAutoSetup.cs
create mode 100644 Assets/Scripts/Vrmtool/ColliderAutoSetup.cs.meta
create mode 100644 Assets/Scripts/Vrmtool/ColliderMoveEditorWindow.cs
create mode 100644 Assets/Scripts/Vrmtool/ColliderMoveEditorWindow.cs.meta
create mode 100644 Assets/Scripts/Vrmtool/ColliderMoveTool.cs
create mode 100644 Assets/Scripts/Vrmtool/ColliderMoveTool.cs.meta
create mode 100644 Assets/Scripts/Vrmtool/RemoveInvalidVRMSpringBones.cs
create mode 100644 Assets/Scripts/Vrmtool/RemoveInvalidVRMSpringBones.cs.meta
create mode 100644 Assets/Scripts/Vrmtool/VRMSpringBoneMoveTool.cs
create mode 100644 Assets/Scripts/Vrmtool/VRMSpringBoneMoveTool.cs.meta
create mode 100644 Assets/Scripts/Vrmtool/VRMToolsWindow.cs
create mode 100644 Assets/Scripts/Vrmtool/VRMToolsWindow.cs.meta
create mode 100644 Assets/test.meta
create mode 100644 Assets/test/GlobalVolumeProfile.asset
create mode 100644 Assets/test/GlobalVolumeProfile.asset.meta
delete mode 100644 StreamDock-Plugin-SDK/Icon/unity.png
delete mode 100644 StreamDock-Plugin-SDK/README_한국어.md
delete mode 100644 StreamDock-Plugin-SDK/SDNodeJsSDK/com.mirabox.streamdock.demo.sdPlugin/package.json
delete mode 100644 StreamDock-Plugin-SDK/SDNodeJsSDK/unity-communication-plugin/manifest.json
delete mode 100644 StreamDock-Plugin-SDK/SDNodeJsSDK/unity-communication-plugin/package.json
delete mode 100644 StreamDock-Plugin-SDK/SDNodeJsSDK/unity-communication-plugin/plugin/app.exe
delete mode 100644 StreamDock-Plugin-SDK/SDNodeJsSDK/unity-communication-plugin/plugin/index.js
delete mode 100644 StreamDock-Plugin-SDK/SDNodeJsSDK/unity-communication-plugin/plugin/package.json
delete mode 100644 StreamDock-Plugin-SDK/SDNodeJsSDK/unity-communication-plugin/propertyInspector/action1/index.html
delete mode 100644 StreamDock-Plugin-SDK/SDNodeJsSDK/unity-communication-plugin/propertyInspector/action1/index.js
delete mode 100644 StreamDock-Plugin-SDK/SDNodeJsSDK/unity-communication-plugin/propertyInspector/action2/index.html
delete mode 100644 StreamDock-Plugin-SDK/SDNodeJsSDK/unity-communication-plugin/readme.md
delete mode 100644 StreamDock-Plugin-SDK/SDNodeJsSDK/unity-communication-plugin/static/@unity.png
delete mode 100644 StreamDock-Plugin-SDK/SDNodeJsSDK/unity-communication-plugin/static/CH.png
delete mode 100644 StreamDock-Plugin-SDK/SDNodeJsSDK/unity-communication-plugin/static/default.jpg
delete mode 100644 StreamDock-Plugin-SDK/SDNodeJsSDK/unity-communication-plugin/zh_CN.json
delete mode 100644 StreamDock-Plugin-SDK/Unity_Scripts/SimpleEventExample.cs
delete mode 100644 StreamDock-Plugin-SDK/Unity_Scripts/SimpleStreamDockCommunicator.cs
delete mode 100644 StreamDock-Plugin-SDK/Unity_StreamDock_통신_가이드.md
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/.gitignore
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/README.md
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/ar.json
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/de.json
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/en.json
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/es.json
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/fr.json
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/it.json
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/ja.json
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/ko.json
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/manifest.json
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/pl.json
rename StreamDock-Plugin-SDK/{SDNodeJsSDK/unity-communication-plugin => com.mirabox.streamdock.weather.sdPlugin}/plugin/autofile.js (70%)
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/plugin/enum.js
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/plugin/index.html
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/plugin/index.js
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/plugin/package.json
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/plugin/utils/weatherService.js
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/propertyInspector/action1/index.html
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/propertyInspector/action1/index.js
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/pt.json
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/ru.json
rename StreamDock-Plugin-SDK/{SDNodeJsSDK/unity-communication-plugin/propertyInspector/utils => com.mirabox.streamdock.weather.sdPlugin/static}/action.js (85%)
rename StreamDock-Plugin-SDK/{SDNodeJsSDK/unity-communication-plugin => com.mirabox.streamdock.weather.sdPlugin}/static/css/caret.svg (100%)
rename StreamDock-Plugin-SDK/{SDNodeJsSDK/unity-communication-plugin => com.mirabox.streamdock.weather.sdPlugin}/static/css/check.png (100%)
rename StreamDock-Plugin-SDK/{SDNodeJsSDK/unity-communication-plugin => com.mirabox.streamdock.weather.sdPlugin}/static/css/check.svg (100%)
rename StreamDock-Plugin-SDK/{SDNodeJsSDK/unity-communication-plugin => com.mirabox.streamdock.weather.sdPlugin}/static/css/elg_calendar.svg (100%)
rename StreamDock-Plugin-SDK/{SDNodeJsSDK/unity-communication-plugin => com.mirabox.streamdock.weather.sdPlugin}/static/css/elg_calendar_inv.svg (100%)
rename StreamDock-Plugin-SDK/{SDNodeJsSDK/unity-communication-plugin => com.mirabox.streamdock.weather.sdPlugin}/static/css/g_d8d8d8.svg (100%)
rename StreamDock-Plugin-SDK/{SDNodeJsSDK/unity-communication-plugin => com.mirabox.streamdock.weather.sdPlugin}/static/css/rcheck.svg (100%)
rename StreamDock-Plugin-SDK/{SDNodeJsSDK/unity-communication-plugin => com.mirabox.streamdock.weather.sdPlugin}/static/css/sdpi.css (100%)
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/100.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/101.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/104.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/150.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/151.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/300.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/302.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/304.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/305.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/306.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/307.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/310.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/311.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/312.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/313.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/350.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/400.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/401.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/402.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/403.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/404.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/407.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/457.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/500.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/502.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/503.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Luxury/508.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/100-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/101-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/102-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/103-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/104-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/150-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/151-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/152-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/153-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/154-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/300-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/301-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/302-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/303-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/304-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/305-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/306-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/307-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/308-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/309-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/310-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/311-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/312-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/313-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/314-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/315-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/316-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/317-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/318-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/350-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/351-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/399-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/400-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/401-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/402-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/403-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/404-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/405-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/406-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/407-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/408-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/409-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/410-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/456-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/457-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/499-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/500-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/501-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/502-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/503-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/504-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/507-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/508-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/509-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/510-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/511-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/512-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/513-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/514-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/515-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/900-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/901-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/Modern/999-fill.svg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/cate.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/default.jpg
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/img/tm.png
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/plugin.js
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/utils/axios.js
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/utils/color.js
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/utils/common.js
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/static/utils/worker.js
create mode 100644 StreamDock-Plugin-SDK/com.mirabox.streamdock.weather.sdPlugin/zh_CN.json
delete mode 100644 StreamDock-Plugin-SDK/개발환경_설정_가이드.md
delete mode 100644 StreamDock-Plugin-SDK/샘플_플러그인_개발_가이드.md
create mode 100644 Streamdeck/Implementation_Guide.md
create mode 100644 Streamdeck/JSON_Protocol_Specification.md
create mode 100644 Streamdeck/Streamingle_Plugin_Architecture.md
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/README.md
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/build.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/debug.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist-dev/com.mirabox.streamingle.sdPlugin/debug.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist-dev/com.mirabox.streamingle.sdPlugin/images/action.png
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist-dev/com.mirabox.streamingle.sdPlugin/manifest.json
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist-dev/com.mirabox.streamingle.sdPlugin/plugin/index.html
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist-dev/com.mirabox.streamingle.sdPlugin/plugin/index.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist-dev/com.mirabox.streamingle.sdPlugin/plugin/log/streamingle_plugin
rename {StreamDock-Plugin-SDK/SDNodeJsSDK/unity-communication-plugin => Streamdeck/com.mirabox.streamingle.sdPlugin/dist-dev/com.mirabox.streamingle.sdPlugin}/plugin/utils/plugin.js (69%)
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist-dev/com.mirabox.streamingle.sdPlugin/propertyInspector/camera/index.html
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist-dev/com.mirabox.streamingle.sdPlugin/propertyInspector/camera/index.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist-dev/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/action.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist-dev/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/axios.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist-dev/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/bootstrap-icons.css
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist-dev/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/bootstrap.min.css
rename {StreamDock-Plugin-SDK/SDNodeJsSDK/unity-communication-plugin => Streamdeck/com.mirabox.streamingle.sdPlugin/dist-dev/com.mirabox.streamingle.sdPlugin}/propertyInspector/utils/common.js (100%)
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist-dev/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/fonts/bootstrap-icons.woff
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist-dev/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/fonts/bootstrap-icons.woff2
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist-dev/com.mirabox.streamingle.sdPlugin/streamingle_plugin
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist-dev/com.mirabox.streamingle.sdPlugin/test.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist/com.mirabox.streamingle.sdPlugin/debug.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist/com.mirabox.streamingle.sdPlugin/images/action.png
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist/com.mirabox.streamingle.sdPlugin/manifest.json
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist/com.mirabox.streamingle.sdPlugin/plugin/index.html
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist/com.mirabox.streamingle.sdPlugin/plugin/index.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist/com.mirabox.streamingle.sdPlugin/plugin/log/streamingle_plugin
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist/com.mirabox.streamingle.sdPlugin/plugin/utils/plugin.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist/com.mirabox.streamingle.sdPlugin/propertyInspector/camera/index.html
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist/com.mirabox.streamingle.sdPlugin/propertyInspector/camera/index.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/action.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/axios.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/bootstrap-icons.css
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/bootstrap.min.css
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/common.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/fonts/bootstrap-icons.woff
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/fonts/bootstrap-icons.woff2
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist/com.mirabox.streamingle.sdPlugin/streamingle_plugin
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/dist/com.mirabox.streamingle.sdPlugin/test.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/images/camera_icon.png
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/images/item_icon.png
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/images/item_icon_inactive.png
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/images/plugin_logo.png
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/manifest.json
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/.package-lock.json
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/ws/LICENSE
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/ws/README.md
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/ws/browser.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/ws/index.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/ws/lib/buffer-util.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/ws/lib/constants.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/ws/lib/event-target.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/ws/lib/extension.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/ws/lib/limiter.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/ws/lib/permessage-deflate.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/ws/lib/receiver.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/ws/lib/sender.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/ws/lib/stream.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/ws/lib/subprotocol.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/ws/lib/validation.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/ws/lib/websocket-server.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/ws/lib/websocket.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/ws/package.json
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/node_modules/ws/wrapper.mjs
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/package-lock.json
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/package.json
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/plugin/index.html
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/plugin/index.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/propertyInspector/camera/index.html
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/propertyInspector/camera/index.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/propertyInspector/item/index.html
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/propertyInspector/item/index.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/action.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/axios.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/bootstrap-icons.css
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/bootstrap.min.css
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/common.js
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/fonts/bootstrap-icons.woff
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/propertyInspector/utils/fonts/bootstrap-icons.woff2
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/streamingle_plugin
create mode 100644 Streamdeck/com.mirabox.streamingle.sdPlugin/test.js
diff --git a/Assets/External/websocket-sharp.meta b/Assets/External/websocket-sharp.meta
new file mode 100644
index 00000000..f9fed47b
--- /dev/null
+++ b/Assets/External/websocket-sharp.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 0b3c479ab08f10f42aaa9bcbad102a3a
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/External/websocket-sharp/AssemblyInfo.cs b/Assets/External/websocket-sharp/AssemblyInfo.cs
new file mode 100644
index 00000000..90c5fa53
--- /dev/null
+++ b/Assets/External/websocket-sharp/AssemblyInfo.cs
@@ -0,0 +1,26 @@
+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("websocket-sharp")]
+[assembly: AssemblyDescription("A C# implementation of the WebSocket protocol client and server")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("websocket-sharp.dll")]
+[assembly: AssemblyCopyright("sta.blockhead")]
+[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.2.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/Assets/External/websocket-sharp/AssemblyInfo.cs.meta b/Assets/External/websocket-sharp/AssemblyInfo.cs.meta
new file mode 100644
index 00000000..293e9191
--- /dev/null
+++ b/Assets/External/websocket-sharp/AssemblyInfo.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 39fd29427d0e18849a32d93c78615e9f
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/ByteOrder.cs b/Assets/External/websocket-sharp/ByteOrder.cs
new file mode 100644
index 00000000..317f462e
--- /dev/null
+++ b/Assets/External/websocket-sharp/ByteOrder.cs
@@ -0,0 +1,47 @@
+#region License
+/*
+ * ByteOrder.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2015 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;
+
+namespace WebSocketSharp
+{
+ ///
+ /// Specifies the byte order.
+ ///
+ public enum ByteOrder
+ {
+ ///
+ /// Specifies Little-endian.
+ ///
+ Little,
+ ///
+ /// Specifies Big-endian.
+ ///
+ Big
+ }
+}
diff --git a/Assets/External/websocket-sharp/ByteOrder.cs.meta b/Assets/External/websocket-sharp/ByteOrder.cs.meta
new file mode 100644
index 00000000..61c0e0a0
--- /dev/null
+++ b/Assets/External/websocket-sharp/ByteOrder.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 87c28a16b5a541a46b5c738f453e85ea
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/CloseEventArgs.cs b/Assets/External/websocket-sharp/CloseEventArgs.cs
new file mode 100644
index 00000000..8aa46e2d
--- /dev/null
+++ b/Assets/External/websocket-sharp/CloseEventArgs.cs
@@ -0,0 +1,118 @@
+#region License
+/*
+ * CloseEventArgs.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2024 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;
+
+namespace WebSocketSharp
+{
+ ///
+ /// Represents the event data for the event.
+ ///
+ ///
+ ///
+ /// The close event occurs when the WebSocket connection has been closed.
+ ///
+ ///
+ /// If you would like to get the reason for the connection close,
+ /// you should access the or
+ /// property.
+ ///
+ ///
+ public class CloseEventArgs : EventArgs
+ {
+ #region Private Fields
+
+ private PayloadData _payloadData;
+ private bool _wasClean;
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal CloseEventArgs (PayloadData payloadData, bool clean)
+ {
+ _payloadData = payloadData;
+ _wasClean = clean;
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the status code for the connection close.
+ ///
+ ///
+ ///
+ /// A that represents the status code for
+ /// the connection close.
+ ///
+ ///
+ /// 1005 (no status) if not present.
+ ///
+ ///
+ public ushort Code {
+ get {
+ return _payloadData.Code;
+ }
+ }
+
+ ///
+ /// Gets the reason for the connection close.
+ ///
+ ///
+ ///
+ /// A that represents the reason for
+ /// the connection close.
+ ///
+ ///
+ /// An empty string if not present.
+ ///
+ ///
+ public string Reason {
+ get {
+ return _payloadData.Reason;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the connection has been closed cleanly.
+ ///
+ ///
+ /// true if the connection has been closed cleanly; otherwise,
+ /// false.
+ ///
+ public bool WasClean {
+ get {
+ return _wasClean;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/CloseEventArgs.cs.meta b/Assets/External/websocket-sharp/CloseEventArgs.cs.meta
new file mode 100644
index 00000000..587f6a9a
--- /dev/null
+++ b/Assets/External/websocket-sharp/CloseEventArgs.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 1d2b6502961bce247ab494e4a6d127c2
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/CloseStatusCode.cs b/Assets/External/websocket-sharp/CloseStatusCode.cs
new file mode 100644
index 00000000..81f3317a
--- /dev/null
+++ b/Assets/External/websocket-sharp/CloseStatusCode.cs
@@ -0,0 +1,120 @@
+#region License
+/*
+ * CloseStatusCode.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2016 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;
+
+namespace WebSocketSharp
+{
+ ///
+ /// Indicates the status code for the WebSocket connection close.
+ ///
+ ///
+ ///
+ /// The values of this enumeration are defined in
+ ///
+ /// Section 7.4 of RFC 6455.
+ ///
+ ///
+ /// "Reserved value" cannot be sent as a status code in
+ /// closing handshake by an endpoint.
+ ///
+ ///
+ public enum CloseStatusCode : ushort
+ {
+ ///
+ /// Equivalent to close status 1000. Indicates normal close.
+ ///
+ Normal = 1000,
+ ///
+ /// Equivalent to close status 1001. Indicates that an endpoint is
+ /// going away.
+ ///
+ Away = 1001,
+ ///
+ /// Equivalent to close status 1002. Indicates that an endpoint is
+ /// terminating the connection due to a protocol error.
+ ///
+ ProtocolError = 1002,
+ ///
+ /// Equivalent to close status 1003. Indicates that an endpoint is
+ /// terminating the connection because it has received a type of
+ /// data that it cannot accept.
+ ///
+ UnsupportedData = 1003,
+ ///
+ /// Equivalent to close status 1004. Still undefined. A Reserved value.
+ ///
+ Undefined = 1004,
+ ///
+ /// Equivalent to close status 1005. Indicates that no status code was
+ /// actually present. A Reserved value.
+ ///
+ NoStatus = 1005,
+ ///
+ /// Equivalent to close status 1006. Indicates that the connection was
+ /// closed abnormally. A Reserved value.
+ ///
+ Abnormal = 1006,
+ ///
+ /// Equivalent to close status 1007. Indicates that an endpoint is
+ /// terminating the connection because it has received a message that
+ /// contains data that is not consistent with the type of the message.
+ ///
+ InvalidData = 1007,
+ ///
+ /// Equivalent to close status 1008. Indicates that an endpoint is
+ /// terminating the connection because it has received a message that
+ /// violates its policy.
+ ///
+ PolicyViolation = 1008,
+ ///
+ /// Equivalent to close status 1009. Indicates that an endpoint is
+ /// terminating the connection because it has received a message that
+ /// is too big to process.
+ ///
+ TooBig = 1009,
+ ///
+ /// Equivalent to close status 1010. Indicates that a client is
+ /// terminating the connection because it has expected the server to
+ /// negotiate one or more extension, but the server did not return
+ /// them in the handshake response.
+ ///
+ MandatoryExtension = 1010,
+ ///
+ /// Equivalent to close status 1011. Indicates that a server is
+ /// terminating the connection because it has encountered an unexpected
+ /// condition that prevented it from fulfilling the request.
+ ///
+ ServerError = 1011,
+ ///
+ /// Equivalent to close status 1015. Indicates that the connection was
+ /// closed due to a failure to perform a TLS handshake. A Reserved value.
+ ///
+ TlsHandshakeFailure = 1015
+ }
+}
diff --git a/Assets/External/websocket-sharp/CloseStatusCode.cs.meta b/Assets/External/websocket-sharp/CloseStatusCode.cs.meta
new file mode 100644
index 00000000..e6461507
--- /dev/null
+++ b/Assets/External/websocket-sharp/CloseStatusCode.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: e8dc83dc35b69e140853c4e18b52b4a8
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/CompressionMethod.cs b/Assets/External/websocket-sharp/CompressionMethod.cs
new file mode 100644
index 00000000..42ab230a
--- /dev/null
+++ b/Assets/External/websocket-sharp/CompressionMethod.cs
@@ -0,0 +1,52 @@
+#region License
+/*
+ * CompressionMethod.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2013-2017 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;
+
+namespace WebSocketSharp
+{
+ ///
+ /// Specifies the method for compression.
+ ///
+ ///
+ /// The methods are defined in
+ ///
+ /// Compression Extensions for WebSocket.
+ ///
+ public enum CompressionMethod : byte
+ {
+ ///
+ /// Specifies no compression.
+ ///
+ None,
+ ///
+ /// Specifies DEFLATE.
+ ///
+ Deflate
+ }
+}
diff --git a/Assets/External/websocket-sharp/CompressionMethod.cs.meta b/Assets/External/websocket-sharp/CompressionMethod.cs.meta
new file mode 100644
index 00000000..ba3f3df5
--- /dev/null
+++ b/Assets/External/websocket-sharp/CompressionMethod.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: f388d1e288415ad45b4bf734a6232341
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/ErrorEventArgs.cs b/Assets/External/websocket-sharp/ErrorEventArgs.cs
new file mode 100644
index 00000000..c02d0e00
--- /dev/null
+++ b/Assets/External/websocket-sharp/ErrorEventArgs.cs
@@ -0,0 +1,115 @@
+#region License
+/*
+ * ErrorEventArgs.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2022 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
+
+#region Contributors
+/*
+ * Contributors:
+ * - Frank Razenberg
+ */
+#endregion
+
+using System;
+
+namespace WebSocketSharp
+{
+ ///
+ /// Represents the event data for the event.
+ ///
+ ///
+ ///
+ /// The error event occurs when the interface
+ /// gets an error.
+ ///
+ ///
+ /// If you would like to get the error message, you should access
+ /// the property.
+ ///
+ ///
+ /// If the error is due to an exception, you can get it by accessing
+ /// the property.
+ ///
+ ///
+ public class ErrorEventArgs : EventArgs
+ {
+ #region Private Fields
+
+ private Exception _exception;
+ private string _message;
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal ErrorEventArgs (string message)
+ : this (message, null)
+ {
+ }
+
+ internal ErrorEventArgs (string message, Exception exception)
+ {
+ _message = message;
+ _exception = exception;
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the exception that caused the error.
+ ///
+ ///
+ ///
+ /// An instance that represents
+ /// the cause of the error.
+ ///
+ ///
+ /// if not present.
+ ///
+ ///
+ public Exception Exception {
+ get {
+ return _exception;
+ }
+ }
+
+ ///
+ /// Gets the error message.
+ ///
+ ///
+ /// A that represents the error message.
+ ///
+ public string Message {
+ get {
+ return _message;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/ErrorEventArgs.cs.meta b/Assets/External/websocket-sharp/ErrorEventArgs.cs.meta
new file mode 100644
index 00000000..4c9e2286
--- /dev/null
+++ b/Assets/External/websocket-sharp/ErrorEventArgs.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 58b5642169f4cce4d8717bd4c8070768
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Ext.cs b/Assets/External/websocket-sharp/Ext.cs
new file mode 100644
index 00000000..2540f803
--- /dev/null
+++ b/Assets/External/websocket-sharp/Ext.cs
@@ -0,0 +1,1902 @@
+#region License
+/*
+ * Ext.cs
+ *
+ * Some parts of this code are derived from Mono (http://www.mono-project.com):
+ * - The GetStatusDescription method is derived from HttpListenerResponse.cs (System.Net)
+ * - The MaybeUri method is derived from Uri.cs (System)
+ * - The isPredefinedScheme method is derived from Uri.cs (System)
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2001 Garrett Rooney
+ * Copyright (c) 2003 Ian MacLean
+ * Copyright (c) 2003 Ben Maurer
+ * Copyright (c) 2003, 2005, 2009 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2009 Stephane Delcroix
+ * Copyright (c) 2010-2025 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
+
+#region Contributors
+/*
+ * Contributors:
+ * - Liryna
+ * - Nikola Kovacevic
+ * - Chris Swiedler
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.IO;
+using System.IO.Compression;
+using System.Net.Sockets;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using WebSocketSharp.Net;
+using WebSocketSharp.Net.WebSockets;
+using WebSocketSharp.Server;
+
+namespace WebSocketSharp
+{
+ ///
+ /// Provides a set of static methods for websocket-sharp.
+ ///
+ public static class Ext
+ {
+ #region Private Fields
+
+ private static readonly byte[] _last = new byte[] { 0x00 };
+ private static readonly int _maxRetry = 5;
+ private const string _tspecials = "()<>@,;:\\\"/[]?={} \t";
+
+ #endregion
+
+ #region Private Methods
+
+ private static byte[] compress (this byte[] data)
+ {
+ if (data.LongLength == 0)
+ return data;
+
+ using (var input = new MemoryStream (data))
+ return input.compressToArray ();
+ }
+
+ private static MemoryStream compress (this Stream stream)
+ {
+ var ret = new MemoryStream ();
+
+ if (stream.Length == 0)
+ return ret;
+
+ stream.Position = 0;
+
+ var mode = CompressionMode.Compress;
+
+ using (var ds = new DeflateStream (ret, mode, true)) {
+ stream.CopyTo (ds, 1024);
+ ds.Close (); // BFINAL set to 1.
+ ret.Write (_last, 0, 1);
+
+ ret.Position = 0;
+
+ return ret;
+ }
+ }
+
+ private static byte[] compressToArray (this Stream stream)
+ {
+ using (var output = stream.compress ()) {
+ output.Close ();
+
+ return output.ToArray ();
+ }
+ }
+
+ private static byte[] decompress (this byte[] data)
+ {
+ if (data.LongLength == 0)
+ return data;
+
+ using (var input = new MemoryStream (data))
+ return input.decompressToArray ();
+ }
+
+ private static MemoryStream decompress (this Stream stream)
+ {
+ var ret = new MemoryStream ();
+
+ if (stream.Length == 0)
+ return ret;
+
+ stream.Position = 0;
+
+ var mode = CompressionMode.Decompress;
+
+ using (var ds = new DeflateStream (stream, mode, true)) {
+ ds.CopyTo (ret, 1024);
+
+ ret.Position = 0;
+
+ return ret;
+ }
+ }
+
+ private static byte[] decompressToArray (this Stream stream)
+ {
+ using (var output = stream.decompress ()) {
+ output.Close ();
+
+ return output.ToArray ();
+ }
+ }
+
+ private static bool isPredefinedScheme (this string value)
+ {
+ var c = value[0];
+
+ if (c == 'h')
+ return value == "http" || value == "https";
+
+ if (c == 'w')
+ return value == "ws" || value == "wss";
+
+ if (c == 'f')
+ return value == "file" || value == "ftp";
+
+ if (c == 'g')
+ return value == "gopher";
+
+ if (c == 'm')
+ return value == "mailto";
+
+ if (c == 'n') {
+ c = value[1];
+
+ return c == 'e'
+ ? value == "news" || value == "net.pipe" || value == "net.tcp"
+ : value == "nntp";
+ }
+
+ return false;
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal static byte[] Append (this ushort code, string reason)
+ {
+ var codeAsBytes = code.ToByteArray (ByteOrder.Big);
+
+ if (reason == null || reason.Length == 0)
+ return codeAsBytes;
+
+ var buff = new List (codeAsBytes);
+ var reasonAsBytes = Encoding.UTF8.GetBytes (reason);
+
+ buff.AddRange (reasonAsBytes);
+
+ return buff.ToArray ();
+ }
+
+ internal static byte[] Compress (
+ this byte[] data,
+ CompressionMethod method
+ )
+ {
+ return method == CompressionMethod.Deflate ? data.compress () : data;
+ }
+
+ internal static Stream Compress (
+ this Stream stream,
+ CompressionMethod method
+ )
+ {
+ return method == CompressionMethod.Deflate ? stream.compress () : stream;
+ }
+
+ internal static bool Contains (this string value, params char[] anyOf)
+ {
+ return anyOf != null && anyOf.Length > 0
+ ? value.IndexOfAny (anyOf) > -1
+ : false;
+ }
+
+ internal static bool Contains (
+ this NameValueCollection collection,
+ string name
+ )
+ {
+ return collection[name] != null;
+ }
+
+ internal static bool Contains (
+ this NameValueCollection collection,
+ string name,
+ string value,
+ StringComparison comparisonTypeForValue
+ )
+ {
+ var val = collection[name];
+
+ if (val == null)
+ return false;
+
+ foreach (var elm in val.Split (',')) {
+ if (elm.Trim ().Equals (value, comparisonTypeForValue))
+ return true;
+ }
+
+ return false;
+ }
+
+ internal static bool Contains (
+ this IEnumerable source,
+ Func condition
+ )
+ {
+ foreach (T elm in source) {
+ if (condition (elm))
+ return true;
+ }
+
+ return false;
+ }
+
+ internal static bool ContainsTwice (this string[] values)
+ {
+ var len = values.Length;
+ var end = len - 1;
+
+ Func seek = null;
+ seek = idx => {
+ if (idx == end)
+ return false;
+
+ var val = values[idx];
+
+ for (var i = idx + 1; i < len; i++) {
+ if (values[i] == val)
+ return true;
+ }
+
+ return seek (++idx);
+ };
+
+ return seek (0);
+ }
+
+ internal static T[] Copy (this T[] sourceArray, int length)
+ {
+ var dest = new T[length];
+
+ Array.Copy (sourceArray, 0, dest, 0, length);
+
+ return dest;
+ }
+
+ internal static T[] Copy (this T[] sourceArray, long length)
+ {
+ var dest = new T[length];
+
+ Array.Copy (sourceArray, 0, dest, 0, length);
+
+ return dest;
+ }
+
+ internal static void CopyTo (
+ this Stream sourceStream,
+ Stream destinationStream,
+ int bufferLength
+ )
+ {
+ var buff = new byte[bufferLength];
+
+ while (true) {
+ var nread = sourceStream.Read (buff, 0, bufferLength);
+
+ if (nread <= 0)
+ break;
+
+ destinationStream.Write (buff, 0, nread);
+ }
+ }
+
+ internal static void CopyToAsync (
+ this Stream sourceStream,
+ Stream destinationStream,
+ int bufferLength,
+ Action completed,
+ Action error
+ )
+ {
+ var buff = new byte[bufferLength];
+
+ AsyncCallback callback = null;
+ callback =
+ ar => {
+ try {
+ var nread = sourceStream.EndRead (ar);
+
+ if (nread <= 0) {
+ if (completed != null)
+ completed ();
+
+ return;
+ }
+
+ destinationStream.Write (buff, 0, nread);
+
+ sourceStream.BeginRead (buff, 0, bufferLength, callback, null);
+ }
+ catch (Exception ex) {
+ if (error != null)
+ error (ex);
+ }
+ };
+
+ try {
+ sourceStream.BeginRead (buff, 0, bufferLength, callback, null);
+ }
+ catch (Exception ex) {
+ if (error != null)
+ error (ex);
+ }
+ }
+
+ internal static byte[] Decompress (
+ this byte[] data,
+ CompressionMethod method
+ )
+ {
+ return method == CompressionMethod.Deflate ? data.decompress () : data;
+ }
+
+ internal static Stream Decompress (
+ this Stream stream,
+ CompressionMethod method
+ )
+ {
+ return method == CompressionMethod.Deflate
+ ? stream.decompress ()
+ : stream;
+ }
+
+ internal static byte[] DecompressToArray (
+ this Stream stream,
+ CompressionMethod method
+ )
+ {
+ return method == CompressionMethod.Deflate
+ ? stream.decompressToArray ()
+ : stream.ToByteArray ();
+ }
+
+ internal static void Emit (
+ this EventHandler eventHandler,
+ object sender,
+ EventArgs e
+ )
+ {
+ if (eventHandler == null)
+ return;
+
+ eventHandler (sender, e);
+ }
+
+ internal static void Emit (
+ this EventHandler eventHandler,
+ object sender,
+ TEventArgs e
+ )
+ where TEventArgs : EventArgs
+ {
+ if (eventHandler == null)
+ return;
+
+ eventHandler (sender, e);
+ }
+
+ internal static string GetAbsolutePath (this Uri uri)
+ {
+ if (uri.IsAbsoluteUri)
+ return uri.AbsolutePath;
+
+ var original = uri.OriginalString;
+
+ if (original[0] != '/')
+ return null;
+
+ var idx = original.IndexOfAny (new[] { '?', '#' });
+
+ return idx > 0 ? original.Substring (0, idx) : original;
+ }
+
+ internal static CookieCollection GetCookies (
+ this NameValueCollection headers,
+ bool response
+ )
+ {
+ var name = response ? "Set-Cookie" : "Cookie";
+ var val = headers[name];
+
+ return val != null
+ ? CookieCollection.Parse (val, response)
+ : new CookieCollection ();
+ }
+
+ internal static string GetDnsSafeHost (this Uri uri, bool bracketIPv6)
+ {
+ return bracketIPv6 && uri.HostNameType == UriHostNameType.IPv6
+ ? uri.Host
+ : uri.DnsSafeHost;
+ }
+
+ internal static string GetErrorMessage (this ushort code)
+ {
+ switch (code) {
+ case 1002:
+ return "A protocol error has occurred.";
+ case 1003:
+ return "Unsupported data has been received.";
+ case 1006:
+ return "An abnormal error has occurred.";
+ case 1007:
+ return "Invalid data has been received.";
+ case 1008:
+ return "A policy violation has occurred.";
+ case 1009:
+ return "A too big message has been received.";
+ case 1010:
+ return "The client did not receive expected extension(s).";
+ case 1011:
+ return "The server got an internal error.";
+ case 1015:
+ return "An error has occurred during a TLS handshake.";
+ default:
+ return String.Empty;
+ }
+ }
+
+ internal static string GetErrorMessage (this CloseStatusCode code)
+ {
+ return ((ushort) code).GetErrorMessage ();
+ }
+
+ internal static string GetName (this string nameAndValue, char separator)
+ {
+ var idx = nameAndValue.IndexOf (separator);
+
+ return idx > 0 ? nameAndValue.Substring (0, idx).Trim () : null;
+ }
+
+ internal static string GetUTF8DecodedString (this byte[] bytes)
+ {
+ try {
+ return Encoding.UTF8.GetString (bytes);
+ }
+ catch {
+ return null;
+ }
+ }
+
+ internal static byte[] GetUTF8EncodedBytes (this string s)
+ {
+ try {
+ return Encoding.UTF8.GetBytes (s);
+ }
+ catch {
+ return null;
+ }
+ }
+
+ internal static string GetValue (this string nameAndValue, char separator)
+ {
+ return nameAndValue.GetValue (separator, false);
+ }
+
+ internal static string GetValue (
+ this string nameAndValue,
+ char separator,
+ bool unquote
+ )
+ {
+ var idx = nameAndValue.IndexOf (separator);
+
+ if (idx < 0 || idx == nameAndValue.Length - 1)
+ return null;
+
+ var val = nameAndValue.Substring (idx + 1).Trim ();
+
+ return unquote ? val.Unquote () : val;
+ }
+
+ internal static bool IsCompressionExtension (
+ this string value,
+ CompressionMethod method
+ )
+ {
+ var extStr = method.ToExtensionString ();
+ var compType = StringComparison.Ordinal;
+
+ return value.StartsWith (extStr, compType);
+ }
+
+ internal static bool IsDefined (this CloseStatusCode code)
+ {
+ return Enum.IsDefined (typeof (CloseStatusCode), code);
+ }
+
+ internal static bool IsEqualTo (
+ this int value,
+ char c,
+ Action beforeComparing
+ )
+ {
+ beforeComparing (value);
+
+ return value == c - 0;
+ }
+
+ internal static bool IsHttpMethod (this string value)
+ {
+ return value == "GET"
+ || value == "HEAD"
+ || value == "POST"
+ || value == "PUT"
+ || value == "DELETE"
+ || value == "CONNECT"
+ || value == "OPTIONS"
+ || value == "TRACE";
+ }
+
+ internal static bool IsPortNumber (this int value)
+ {
+ return value > 0 && value < 65536;
+ }
+
+ internal static bool IsReserved (this CloseStatusCode code)
+ {
+ return ((ushort) code).IsReservedStatusCode ();
+ }
+
+ internal static bool IsReservedStatusCode (this ushort code)
+ {
+ return code == 1004
+ || code == 1005
+ || code == 1006
+ || code == 1015;
+ }
+
+ internal static bool IsSupportedOpcode (this int opcode)
+ {
+ return Enum.IsDefined (typeof (Opcode), opcode);
+ }
+
+ internal static bool IsText (this string value)
+ {
+ var len = value.Length;
+
+ for (var i = 0; i < len; i++) {
+ var c = value[i];
+
+ if (c < 0x20) {
+ if ("\r\n\t".IndexOf (c) == -1)
+ return false;
+
+ if (c == '\n') {
+ i++;
+
+ if (i == len)
+ break;
+
+ c = value[i];
+
+ if (" \t".IndexOf (c) == -1)
+ return false;
+ }
+
+ continue;
+ }
+
+ if (c == 0x7f)
+ return false;
+ }
+
+ return true;
+ }
+
+ internal static bool IsToken (this string value)
+ {
+ foreach (var c in value) {
+ if (c < 0x20)
+ return false;
+
+ if (c > 0x7e)
+ return false;
+
+ if (_tspecials.IndexOf (c) > -1)
+ return false;
+ }
+
+ return true;
+ }
+
+ internal static bool KeepsAlive (
+ this NameValueCollection headers,
+ Version version
+ )
+ {
+ var compType = StringComparison.OrdinalIgnoreCase;
+
+ return version > HttpVersion.Version10
+ ? !headers.Contains ("Connection", "close", compType)
+ : headers.Contains ("Connection", "keep-alive", compType);
+ }
+
+ internal static bool MaybeUri (this string value)
+ {
+ var idx = value.IndexOf (':');
+
+ if (idx < 2 || idx > 9)
+ return false;
+
+ var schm = value.Substring (0, idx);
+
+ return schm.isPredefinedScheme ();
+ }
+
+ internal static string Quote (this string value)
+ {
+ var fmt = "\"{0}\"";
+ var val = value.Replace ("\"", "\\\"");
+
+ return String.Format (fmt, val);
+ }
+
+ internal static byte[] ReadBytes (this Stream stream, int length)
+ {
+ var ret = new byte[length];
+
+ var offset = 0;
+ var retry = 0;
+
+ while (length > 0) {
+ var nread = stream.Read (ret, offset, length);
+
+ if (nread <= 0) {
+ if (retry < _maxRetry) {
+ retry++;
+
+ continue;
+ }
+
+ return ret.SubArray (0, offset);
+ }
+
+ retry = 0;
+
+ offset += nread;
+ length -= nread;
+ }
+
+ return ret;
+ }
+
+ internal static byte[] ReadBytes (
+ this Stream stream,
+ long length,
+ int bufferLength
+ )
+ {
+ using (var dest = new MemoryStream ()) {
+ var buff = new byte[bufferLength];
+ var retry = 0;
+
+ while (length > 0) {
+ if (length < bufferLength)
+ bufferLength = (int) length;
+
+ var nread = stream.Read (buff, 0, bufferLength);
+
+ if (nread <= 0) {
+ if (retry < _maxRetry) {
+ retry++;
+
+ continue;
+ }
+
+ break;
+ }
+
+ retry = 0;
+
+ dest.Write (buff, 0, nread);
+
+ length -= nread;
+ }
+
+ dest.Close ();
+
+ return dest.ToArray ();
+ }
+ }
+
+ internal static void ReadBytesAsync (
+ this Stream stream,
+ int length,
+ Action completed,
+ Action error
+ )
+ {
+ var ret = new byte[length];
+
+ var offset = 0;
+ var retry = 0;
+
+ AsyncCallback callback = null;
+ callback =
+ ar => {
+ try {
+ var nread = stream.EndRead (ar);
+
+ if (nread <= 0) {
+ if (retry < _maxRetry) {
+ retry++;
+
+ stream.BeginRead (ret, offset, length, callback, null);
+
+ return;
+ }
+
+ if (completed != null)
+ completed (ret.SubArray (0, offset));
+
+ return;
+ }
+
+ if (nread == length) {
+ if (completed != null)
+ completed (ret);
+
+ return;
+ }
+
+ retry = 0;
+
+ offset += nread;
+ length -= nread;
+
+ stream.BeginRead (ret, offset, length, callback, null);
+ }
+ catch (Exception ex) {
+ if (error != null)
+ error (ex);
+ }
+ };
+
+ try {
+ stream.BeginRead (ret, offset, length, callback, null);
+ }
+ catch (Exception ex) {
+ if (error != null)
+ error (ex);
+ }
+ }
+
+ internal static void ReadBytesAsync (
+ this Stream stream,
+ long length,
+ int bufferLength,
+ Action completed,
+ Action error
+ )
+ {
+ var dest = new MemoryStream ();
+
+ var buff = new byte[bufferLength];
+ var retry = 0;
+
+ Action read = null;
+ read =
+ len => {
+ if (len < bufferLength)
+ bufferLength = (int) len;
+
+ stream.BeginRead (
+ buff,
+ 0,
+ bufferLength,
+ ar => {
+ try {
+ var nread = stream.EndRead (ar);
+
+ if (nread <= 0) {
+ if (retry < _maxRetry) {
+ retry++;
+
+ read (len);
+
+ return;
+ }
+
+ if (completed != null) {
+ dest.Close ();
+
+ var ret = dest.ToArray ();
+
+ completed (ret);
+ }
+
+ dest.Dispose ();
+
+ return;
+ }
+
+ dest.Write (buff, 0, nread);
+
+ if (nread == len) {
+ if (completed != null) {
+ dest.Close ();
+
+ var ret = dest.ToArray ();
+
+ completed (ret);
+ }
+
+ dest.Dispose ();
+
+ return;
+ }
+
+ retry = 0;
+
+ read (len - nread);
+ }
+ catch (Exception ex) {
+ dest.Dispose ();
+
+ if (error != null)
+ error (ex);
+ }
+ },
+ null
+ );
+ };
+
+ try {
+ read (length);
+ }
+ catch (Exception ex) {
+ dest.Dispose ();
+
+ if (error != null)
+ error (ex);
+ }
+ }
+
+ internal static T[] Reverse (this T[] array)
+ {
+ var len = array.LongLength;
+ var ret = new T[len];
+
+ var end = len - 1;
+
+ for (long i = 0; i <= end; i++)
+ ret[i] = array[end - i];
+
+ return ret;
+ }
+
+ internal static IEnumerable SplitHeaderValue (
+ this string value,
+ params char[] separators
+ )
+ {
+ var len = value.Length;
+ var end = len - 1;
+
+ var buff = new StringBuilder (32);
+ var escaped = false;
+ var quoted = false;
+
+ for (var i = 0; i <= end; i++) {
+ var c = value[i];
+
+ buff.Append (c);
+
+ if (c == '"') {
+ if (escaped) {
+ escaped = false;
+
+ continue;
+ }
+
+ quoted = !quoted;
+
+ continue;
+ }
+
+ if (c == '\\') {
+ if (i == end)
+ break;
+
+ if (value[i + 1] == '"')
+ escaped = true;
+
+ continue;
+ }
+
+ if (Array.IndexOf (separators, c) > -1) {
+ if (quoted)
+ continue;
+
+ buff.Length -= 1;
+
+ yield return buff.ToString ();
+
+ buff.Length = 0;
+
+ continue;
+ }
+ }
+
+ yield return buff.ToString ();
+ }
+
+ internal static byte[] ToByteArray (this Stream stream)
+ {
+ stream.Position = 0;
+
+ using (var buff = new MemoryStream ()) {
+ stream.CopyTo (buff, 1024);
+ buff.Close ();
+
+ return buff.ToArray ();
+ }
+ }
+
+ internal static byte[] ToByteArray (this ushort value, ByteOrder order)
+ {
+ var ret = BitConverter.GetBytes (value);
+
+ if (!order.IsHostOrder ())
+ Array.Reverse (ret);
+
+ return ret;
+ }
+
+ internal static byte[] ToByteArray (this ulong value, ByteOrder order)
+ {
+ var ret = BitConverter.GetBytes (value);
+
+ if (!order.IsHostOrder ())
+ Array.Reverse (ret);
+
+ return ret;
+ }
+
+ internal static CompressionMethod ToCompressionMethod (this string value)
+ {
+ var methods = Enum.GetValues (typeof (CompressionMethod));
+
+ foreach (CompressionMethod method in methods) {
+ if (method.ToExtensionString () == value)
+ return method;
+ }
+
+ return CompressionMethod.None;
+ }
+
+ internal static string ToExtensionString (
+ this CompressionMethod method,
+ params string[] parameters
+ )
+ {
+ if (method == CompressionMethod.None)
+ return String.Empty;
+
+ var name = method.ToString ().ToLower ();
+ var ename = String.Format ("permessage-{0}", name);
+
+ if (parameters == null || parameters.Length == 0)
+ return ename;
+
+ var eparams = parameters.ToString ("; ");
+
+ return String.Format ("{0}; {1}", ename, eparams);
+ }
+
+ internal static int ToInt32 (this string numericString)
+ {
+ return Int32.Parse (numericString);
+ }
+
+ internal static System.Net.IPAddress ToIPAddress (this string value)
+ {
+ if (value == null || value.Length == 0)
+ return null;
+
+ System.Net.IPAddress addr;
+
+ if (System.Net.IPAddress.TryParse (value, out addr))
+ return addr;
+
+ try {
+ var addrs = System.Net.Dns.GetHostAddresses (value);
+
+ return addrs[0];
+ }
+ catch {
+ return null;
+ }
+ }
+
+ internal static List ToList (
+ this IEnumerable source
+ )
+ {
+ return new List (source);
+ }
+
+ internal static string ToString (
+ this System.Net.IPAddress address,
+ bool bracketIPv6
+ )
+ {
+ return bracketIPv6
+ && address.AddressFamily == AddressFamily.InterNetworkV6
+ ? String.Format ("[{0}]", address)
+ : address.ToString ();
+ }
+
+ internal static ushort ToUInt16 (this byte[] source, ByteOrder sourceOrder)
+ {
+ var val = source.ToHostOrder (sourceOrder);
+
+ return BitConverter.ToUInt16 (val, 0);
+ }
+
+ internal static ulong ToUInt64 (this byte[] source, ByteOrder sourceOrder)
+ {
+ var val = source.ToHostOrder (sourceOrder);
+
+ return BitConverter.ToUInt64 (val, 0);
+ }
+
+ internal static Version ToVersion (this string versionString)
+ {
+ return new Version (versionString);
+ }
+
+ internal static IEnumerable TrimEach (
+ this IEnumerable source
+ )
+ {
+ foreach (var elm in source)
+ yield return elm.Trim ();
+ }
+
+ internal static string TrimSlashFromEnd (this string value)
+ {
+ var ret = value.TrimEnd ('/');
+
+ return ret.Length > 0 ? ret : "/";
+ }
+
+ internal static string TrimSlashOrBackslashFromEnd (this string value)
+ {
+ var ret = value.TrimEnd ('/', '\\');
+
+ return ret.Length > 0 ? ret : value[0].ToString ();
+ }
+
+ internal static bool TryCreateVersion (
+ this string versionString,
+ out Version result
+ )
+ {
+ result = null;
+
+ try {
+ result = new Version (versionString);
+ }
+ catch {
+ return false;
+ }
+
+ return true;
+ }
+
+ internal static bool TryCreateWebSocketUri (
+ this string uriString,
+ out Uri result,
+ out string message
+ )
+ {
+ result = null;
+ message = null;
+
+ var uri = uriString.ToUri ();
+
+ if (uri == null) {
+ message = "An invalid URI string.";
+
+ return false;
+ }
+
+ if (!uri.IsAbsoluteUri) {
+ message = "A relative URI.";
+
+ return false;
+ }
+
+ var schm = uri.Scheme;
+ var valid = schm == "ws" || schm == "wss";
+
+ if (!valid) {
+ message = "The scheme part is not \"ws\" or \"wss\".";
+
+ return false;
+ }
+
+ var port = uri.Port;
+
+ if (port == 0) {
+ message = "The port part is zero.";
+
+ return false;
+ }
+
+ if (uri.Fragment.Length > 0) {
+ message = "It includes the fragment component.";
+
+ return false;
+ }
+
+ if (port == -1) {
+ port = schm == "ws" ? 80 : 443;
+ uriString = String.Format (
+ "{0}://{1}:{2}{3}",
+ schm,
+ uri.Host,
+ port,
+ uri.PathAndQuery
+ );
+
+ result = new Uri (uriString);
+ }
+ else {
+ result = uri;
+ }
+
+ return true;
+ }
+
+ internal static bool TryGetUTF8DecodedString (
+ this byte[] bytes,
+ out string s
+ )
+ {
+ s = null;
+
+ try {
+ s = Encoding.UTF8.GetString (bytes);
+ }
+ catch {
+ return false;
+ }
+
+ return true;
+ }
+
+ internal static bool TryGetUTF8EncodedBytes (
+ this string s,
+ out byte[] bytes
+ )
+ {
+ bytes = null;
+
+ try {
+ bytes = Encoding.UTF8.GetBytes (s);
+ }
+ catch {
+ return false;
+ }
+
+ return true;
+ }
+
+ internal static bool TryOpenRead (
+ this FileInfo fileInfo,
+ out FileStream fileStream
+ )
+ {
+ fileStream = null;
+
+ try {
+ fileStream = fileInfo.OpenRead ();
+ }
+ catch {
+ return false;
+ }
+
+ return true;
+ }
+
+ internal static string Unquote (this string value)
+ {
+ var first = value.IndexOf ('"');
+
+ if (first == -1)
+ return value;
+
+ var last = value.LastIndexOf ('"');
+
+ if (last == first)
+ return value;
+
+ var len = last - first - 1;
+
+ return len > 0
+ ? value.Substring (first + 1, len).Replace ("\\\"", "\"")
+ : String.Empty;
+ }
+
+ internal static bool Upgrades (
+ this NameValueCollection headers,
+ string protocol
+ )
+ {
+ var compType = StringComparison.OrdinalIgnoreCase;
+
+ return headers.Contains ("Upgrade", protocol, compType)
+ && headers.Contains ("Connection", "Upgrade", compType);
+ }
+
+ internal static string UrlDecode (this string value, Encoding encoding)
+ {
+ return value.IndexOfAny (new[] { '%', '+' }) > -1
+ ? HttpUtility.UrlDecode (value, encoding)
+ : value;
+ }
+
+ internal static string UrlEncode (this string value, Encoding encoding)
+ {
+ return HttpUtility.UrlEncode (value, encoding);
+ }
+
+ internal static void WriteBytes (
+ this Stream stream,
+ byte[] bytes,
+ int bufferLength
+ )
+ {
+ using (var src = new MemoryStream (bytes))
+ src.CopyTo (stream, bufferLength);
+ }
+
+ internal static void WriteBytesAsync (
+ this Stream stream,
+ byte[] bytes,
+ int bufferLength,
+ Action completed,
+ Action error
+ )
+ {
+ var src = new MemoryStream (bytes);
+
+ src.CopyToAsync (
+ stream,
+ bufferLength,
+ () => {
+ if (completed != null)
+ completed ();
+
+ src.Dispose ();
+ },
+ ex => {
+ src.Dispose ();
+
+ if (error != null)
+ error (ex);
+ }
+ );
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Gets the description of the specified HTTP status code.
+ ///
+ ///
+ /// A that represents the description of
+ /// the HTTP status code.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It specifies the HTTP status code.
+ ///
+ ///
+ public static string GetDescription (this HttpStatusCode code)
+ {
+ return ((int) code).GetStatusDescription ();
+ }
+
+ ///
+ /// Gets the description of the specified HTTP status code.
+ ///
+ ///
+ ///
+ /// A that represents the description of
+ /// the HTTP status code.
+ ///
+ ///
+ /// An empty string if the description is not present.
+ ///
+ ///
+ ///
+ /// An that specifies the HTTP status code.
+ ///
+ public static string GetStatusDescription (this 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 String.Empty;
+ }
+
+ ///
+ /// Determines whether the specified ushort is in the range of
+ /// the status code for the WebSocket connection close.
+ ///
+ ///
+ ///
+ /// The ranges are the following:
+ ///
+ ///
+ /// -
+ ///
+ /// 1000-2999: These numbers are reserved for definition by
+ /// the WebSocket protocol.
+ ///
+ ///
+ /// -
+ ///
+ /// 3000-3999: These numbers are reserved for use by libraries,
+ /// frameworks, and applications.
+ ///
+ ///
+ /// -
+ ///
+ /// 4000-4999: These numbers are reserved for private use.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// true if is in the range of
+ /// the status code for the close; otherwise, false.
+ ///
+ ///
+ /// A to test.
+ ///
+ public static bool IsCloseStatusCode (this ushort value)
+ {
+ return value > 999 && value < 5000;
+ }
+
+ ///
+ /// Determines whether the specified string is enclosed in
+ /// the specified character.
+ ///
+ ///
+ /// true if is enclosed in
+ /// ; otherwise, false.
+ ///
+ ///
+ /// A to test.
+ ///
+ ///
+ /// A to find.
+ ///
+ public static bool IsEnclosedIn (this string value, char c)
+ {
+ if (value == null)
+ return false;
+
+ var len = value.Length;
+
+ return len > 1 ? value[0] == c && value[len - 1] == c : false;
+ }
+
+ ///
+ /// Determines whether the specified byte order is host (this computer
+ /// architecture) byte order.
+ ///
+ ///
+ /// true if is host byte order; otherwise,
+ /// false.
+ ///
+ ///
+ /// One of the enum values to test.
+ ///
+ public static bool IsHostOrder (this ByteOrder order)
+ {
+ // true: !(true ^ true) or !(false ^ false)
+ // false: !(true ^ false) or !(false ^ true)
+ return !(BitConverter.IsLittleEndian ^ (order == ByteOrder.Little));
+ }
+
+ ///
+ /// Determines whether the specified IP address is a local IP address.
+ ///
+ ///
+ /// This local means NOT REMOTE for the current host.
+ ///
+ ///
+ /// true if is a local IP address;
+ /// otherwise, false.
+ ///
+ ///
+ /// A to test.
+ ///
+ ///
+ /// is .
+ ///
+ public static bool IsLocal (this System.Net.IPAddress address)
+ {
+ if (address == null)
+ throw new ArgumentNullException ("address");
+
+ if (address.Equals (System.Net.IPAddress.Any))
+ return true;
+
+ if (address.Equals (System.Net.IPAddress.Loopback))
+ return true;
+
+ if (Socket.OSSupportsIPv6) {
+ if (address.Equals (System.Net.IPAddress.IPv6Any))
+ return true;
+
+ if (address.Equals (System.Net.IPAddress.IPv6Loopback))
+ return true;
+ }
+
+ var name = System.Net.Dns.GetHostName ();
+ var addrs = System.Net.Dns.GetHostAddresses (name);
+
+ foreach (var addr in addrs) {
+ if (address.Equals (addr))
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Determines whether the specified string is or
+ /// an empty string.
+ ///
+ ///
+ /// true if is or
+ /// an empty string; otherwise, false.
+ ///
+ ///
+ /// A to test.
+ ///
+ public static bool IsNullOrEmpty (this string value)
+ {
+ return value == null || value.Length == 0;
+ }
+
+ ///
+ /// Retrieves a sub-array from the specified array. A sub-array starts at
+ /// the specified index in the array.
+ ///
+ ///
+ /// An array of T that receives a sub-array.
+ ///
+ ///
+ /// An array of T from which to retrieve a sub-array.
+ ///
+ ///
+ /// An that specifies the zero-based index in the array
+ /// at which retrieving starts.
+ ///
+ ///
+ /// An that specifies the number of elements to retrieve.
+ ///
+ ///
+ /// The type of elements in the array.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// is less than zero.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is greater than the end of the array.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is less than zero.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is greater than the number of elements from
+ /// to the end of the array.
+ ///
+ ///
+ public static T[] SubArray (this T[] array, int startIndex, int length)
+ {
+ if (array == null)
+ throw new ArgumentNullException ("array");
+
+ var len = array.Length;
+
+ if (len == 0) {
+ if (startIndex != 0)
+ throw new ArgumentOutOfRangeException ("startIndex");
+
+ if (length != 0)
+ throw new ArgumentOutOfRangeException ("length");
+
+ return array;
+ }
+
+ if (startIndex < 0 || startIndex >= len)
+ throw new ArgumentOutOfRangeException ("startIndex");
+
+ if (length < 0 || length > len - startIndex)
+ throw new ArgumentOutOfRangeException ("length");
+
+ if (length == 0)
+ return new T[0];
+
+ if (length == len)
+ return array;
+
+ var ret = new T[length];
+
+ Array.Copy (array, startIndex, ret, 0, length);
+
+ return ret;
+ }
+
+ ///
+ /// Retrieves a sub-array from the specified array. A sub-array starts at
+ /// the specified index in the array.
+ ///
+ ///
+ /// An array of T that receives a sub-array.
+ ///
+ ///
+ /// An array of T from which to retrieve a sub-array.
+ ///
+ ///
+ /// A that specifies the zero-based index in the array
+ /// at which retrieving starts.
+ ///
+ ///
+ /// A that specifies the number of elements to retrieve.
+ ///
+ ///
+ /// The type of elements in the array.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// is less than zero.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is greater than the end of the array.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is less than zero.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is greater than the number of elements from
+ /// to the end of the array.
+ ///
+ ///
+ public static T[] SubArray (this T[] array, long startIndex, long length)
+ {
+ if (array == null)
+ throw new ArgumentNullException ("array");
+
+ var len = array.LongLength;
+
+ if (len == 0) {
+ if (startIndex != 0)
+ throw new ArgumentOutOfRangeException ("startIndex");
+
+ if (length != 0)
+ throw new ArgumentOutOfRangeException ("length");
+
+ return array;
+ }
+
+ if (startIndex < 0 || startIndex >= len)
+ throw new ArgumentOutOfRangeException ("startIndex");
+
+ if (length < 0 || length > len - startIndex)
+ throw new ArgumentOutOfRangeException ("length");
+
+ if (length == 0)
+ return new T[0];
+
+ if (length == len)
+ return array;
+
+ var ret = new T[length];
+
+ Array.Copy (array, startIndex, ret, 0, length);
+
+ return ret;
+ }
+
+ ///
+ /// Executes the specified delegate times.
+ ///
+ ///
+ /// An that specifies the number of times to execute.
+ ///
+ ///
+ ///
+ /// An Action<int> delegate to execute.
+ ///
+ ///
+ /// The parameter is the zero-based count of iteration.
+ ///
+ ///
+ public static void Times (this int n, Action action)
+ {
+ if (n <= 0)
+ return;
+
+ if (action == null)
+ return;
+
+ for (int i = 0; i < n; i++)
+ action (i);
+ }
+
+ ///
+ /// Executes the specified delegate times.
+ ///
+ ///
+ /// A that specifies the number of times to execute.
+ ///
+ ///
+ ///
+ /// An Action<long> delegate to execute.
+ ///
+ ///
+ /// The parameter is the zero-based count of iteration.
+ ///
+ ///
+ public static void Times (this long n, Action action)
+ {
+ if (n <= 0)
+ return;
+
+ if (action == null)
+ return;
+
+ for (long i = 0; i < n; i++)
+ action (i);
+ }
+
+ ///
+ /// Converts the order of elements in the specified byte array to
+ /// host (this computer architecture) byte order.
+ ///
+ ///
+ ///
+ /// An array of converted from
+ /// .
+ ///
+ ///
+ /// if the number of elements in
+ /// it is less than 2 or is
+ /// same as host byte order.
+ ///
+ ///
+ ///
+ /// An array of to convert.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It specifies the order of elements in .
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public static byte[] ToHostOrder (this byte[] source, ByteOrder sourceOrder)
+ {
+ if (source == null)
+ throw new ArgumentNullException ("source");
+
+ if (source.Length < 2)
+ return source;
+
+ if (sourceOrder.IsHostOrder ())
+ return source;
+
+ return source.Reverse ();
+ }
+
+ ///
+ /// Converts the specified array to a string.
+ ///
+ ///
+ ///
+ /// A converted by concatenating each element of
+ /// across .
+ ///
+ ///
+ /// An empty string if is an empty array.
+ ///
+ ///
+ ///
+ /// An array of T to convert.
+ ///
+ ///
+ /// A used to separate each element of
+ /// .
+ ///
+ ///
+ /// The type of elements in .
+ ///
+ ///
+ /// is .
+ ///
+ public static string ToString (this T[] array, string separator)
+ {
+ if (array == null)
+ throw new ArgumentNullException ("array");
+
+ var len = array.Length;
+
+ if (len == 0)
+ return String.Empty;
+
+ var buff = new StringBuilder (64);
+ var end = len - 1;
+
+ for (var i = 0; i < end; i++)
+ buff.AppendFormat ("{0}{1}", array[i], separator);
+
+ buff.AppendFormat ("{0}", array[end]);
+
+ return buff.ToString ();
+ }
+
+ ///
+ /// Converts the specified string to a .
+ ///
+ ///
+ ///
+ /// A converted from .
+ ///
+ ///
+ /// if the conversion has failed.
+ ///
+ ///
+ ///
+ /// A to convert.
+ ///
+ public static Uri ToUri (this string value)
+ {
+ if (value == null || value.Length == 0)
+ return null;
+
+ var kind = value.MaybeUri () ? UriKind.Absolute : UriKind.Relative;
+ Uri ret;
+
+ Uri.TryCreate (value, kind, out ret);
+
+ return ret;
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Ext.cs.meta b/Assets/External/websocket-sharp/Ext.cs.meta
new file mode 100644
index 00000000..1aa38545
--- /dev/null
+++ b/Assets/External/websocket-sharp/Ext.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 6c78a6051f0f6ab44806b2b480347ea4
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Fin.cs b/Assets/External/websocket-sharp/Fin.cs
new file mode 100644
index 00000000..36622d7e
--- /dev/null
+++ b/Assets/External/websocket-sharp/Fin.cs
@@ -0,0 +1,52 @@
+#region License
+/*
+ * Fin.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2025 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;
+
+namespace WebSocketSharp
+{
+ ///
+ /// Indicates whether a WebSocket frame is the final frame of a message.
+ ///
+ ///
+ /// The values of this enumeration are defined in
+ ///
+ /// Section 5.2 of RFC 6455.
+ ///
+ internal enum Fin
+ {
+ ///
+ /// Equivalent to numeric value 0. Indicates more frames of a message follow.
+ ///
+ More = 0x0,
+ ///
+ /// Equivalent to numeric value 1. Indicates the final frame of a message.
+ ///
+ Final = 0x1
+ }
+}
diff --git a/Assets/External/websocket-sharp/Fin.cs.meta b/Assets/External/websocket-sharp/Fin.cs.meta
new file mode 100644
index 00000000..49e742df
--- /dev/null
+++ b/Assets/External/websocket-sharp/Fin.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 8581c072d74d61c469dc413061917da4
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/HttpBase.cs b/Assets/External/websocket-sharp/HttpBase.cs
new file mode 100644
index 00000000..c4a244f4
--- /dev/null
+++ b/Assets/External/websocket-sharp/HttpBase.cs
@@ -0,0 +1,317 @@
+#region License
+/*
+ * HttpBase.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2024 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.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using WebSocketSharp.Net;
+
+namespace WebSocketSharp
+{
+ internal abstract class HttpBase
+ {
+ #region Private Fields
+
+ private NameValueCollection _headers;
+ private static readonly int _maxMessageHeaderLength;
+ private string _messageBody;
+ private byte[] _messageBodyData;
+ private Version _version;
+
+ #endregion
+
+ #region Protected Fields
+
+ protected static readonly string CrLf;
+ protected static readonly string CrLfHt;
+ protected static readonly string CrLfSp;
+
+ #endregion
+
+ #region Static Constructor
+
+ static HttpBase ()
+ {
+ _maxMessageHeaderLength = 8192;
+
+ CrLf = "\r\n";
+ CrLfHt = "\r\n\t";
+ CrLfSp = "\r\n ";
+ }
+
+ #endregion
+
+ #region Protected Constructors
+
+ protected HttpBase (Version version, NameValueCollection headers)
+ {
+ _version = version;
+ _headers = headers;
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal byte[] MessageBodyData {
+ get {
+ return _messageBodyData;
+ }
+ }
+
+ #endregion
+
+ #region Protected Properties
+
+ protected string HeaderSection {
+ get {
+ var buff = new StringBuilder (64);
+
+ var fmt = "{0}: {1}{2}";
+
+ foreach (var key in _headers.AllKeys)
+ buff.AppendFormat (fmt, key, _headers[key], CrLf);
+
+ buff.Append (CrLf);
+
+ return buff.ToString ();
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ public bool HasMessageBody {
+ get {
+ return _messageBodyData != null;
+ }
+ }
+
+ public NameValueCollection Headers {
+ get {
+ return _headers;
+ }
+ }
+
+ public string MessageBody {
+ get {
+ if (_messageBody == null)
+ _messageBody = getMessageBody ();
+
+ return _messageBody;
+ }
+ }
+
+ public abstract string MessageHeader { get; }
+
+ public Version ProtocolVersion {
+ get {
+ return _version;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private string getMessageBody ()
+ {
+ if (_messageBodyData == null || _messageBodyData.LongLength == 0)
+ return String.Empty;
+
+ var contentType = _headers["Content-Type"];
+
+ var enc = contentType != null && contentType.Length > 0
+ ? HttpUtility.GetEncoding (contentType)
+ : Encoding.UTF8;
+
+ return enc.GetString (_messageBodyData);
+ }
+
+ private static byte[] readMessageBodyFrom (Stream stream, string length)
+ {
+ long len;
+
+ if (!Int64.TryParse (length, out len)) {
+ var msg = "It could not be parsed.";
+
+ throw new ArgumentException (msg, "length");
+ }
+
+ if (len < 0) {
+ var msg = "Less than zero.";
+
+ throw new ArgumentOutOfRangeException ("length", msg);
+ }
+
+ return len > 1024
+ ? stream.ReadBytes (len, 1024)
+ : len > 0
+ ? stream.ReadBytes ((int) len)
+ : null;
+ }
+
+ private static string[] readMessageHeaderFrom (Stream stream)
+ {
+ var buff = new List ();
+ var cnt = 0;
+ Action add =
+ i => {
+ if (i == -1) {
+ var msg = "The header could not be read from the data stream.";
+
+ throw new EndOfStreamException (msg);
+ }
+
+ buff.Add ((byte) i);
+
+ cnt++;
+ };
+
+ var end = false;
+
+ do {
+ end = stream.ReadByte ().IsEqualTo ('\r', add)
+ && stream.ReadByte ().IsEqualTo ('\n', add)
+ && stream.ReadByte ().IsEqualTo ('\r', add)
+ && stream.ReadByte ().IsEqualTo ('\n', add);
+
+ if (cnt > _maxMessageHeaderLength) {
+ var msg = "The length of the header is greater than the max length.";
+
+ throw new InvalidOperationException (msg);
+ }
+ }
+ while (!end);
+
+ var bytes = buff.ToArray ();
+
+ return Encoding.UTF8.GetString (bytes)
+ .Replace (CrLfSp, " ")
+ .Replace (CrLfHt, " ")
+ .Split (new[] { CrLf }, StringSplitOptions.RemoveEmptyEntries);
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal void WriteTo (Stream stream)
+ {
+ var bytes = ToByteArray ();
+
+ stream.Write (bytes, 0, bytes.Length);
+ }
+
+ #endregion
+
+ #region Protected Methods
+
+ protected static T Read (
+ Stream stream,
+ Func parser,
+ int millisecondsTimeout
+ )
+ where T : HttpBase
+ {
+ T ret = null;
+
+ var timeout = false;
+ var timer = new Timer (
+ state => {
+ timeout = true;
+
+ stream.Close ();
+ },
+ null,
+ millisecondsTimeout,
+ -1
+ );
+
+ Exception exception = null;
+
+ try {
+ var header = readMessageHeaderFrom (stream);
+ ret = parser (header);
+
+ var contentLen = ret.Headers["Content-Length"];
+
+ if (contentLen != null && contentLen.Length > 0)
+ ret._messageBodyData = readMessageBodyFrom (stream, contentLen);
+ }
+ catch (Exception ex) {
+ exception = ex;
+ }
+ finally {
+ timer.Change (-1, -1);
+ timer.Dispose ();
+ }
+
+ if (timeout) {
+ var msg = "A timeout has occurred.";
+
+ throw new WebSocketException (msg);
+ }
+
+ if (exception != null) {
+ var msg = "An exception has occurred.";
+
+ throw new WebSocketException (msg, exception);
+ }
+
+ return ret;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public byte[] ToByteArray ()
+ {
+ var headerData = Encoding.UTF8.GetBytes (MessageHeader);
+
+ return _messageBodyData != null
+ ? headerData.Concat (_messageBodyData).ToArray ()
+ : headerData;
+ }
+
+ public override string ToString ()
+ {
+ return _messageBodyData != null
+ ? MessageHeader + MessageBody
+ : MessageHeader;
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/HttpBase.cs.meta b/Assets/External/websocket-sharp/HttpBase.cs.meta
new file mode 100644
index 00000000..55ff373a
--- /dev/null
+++ b/Assets/External/websocket-sharp/HttpBase.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 01336e40da8ed304fa4f40db4660af86
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/HttpRequest.cs b/Assets/External/websocket-sharp/HttpRequest.cs
new file mode 100644
index 00000000..dd51d010
--- /dev/null
+++ b/Assets/External/websocket-sharp/HttpRequest.cs
@@ -0,0 +1,253 @@
+#region License
+/*
+ * HttpRequest.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2024 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
+
+#region Contributors
+/*
+ * Contributors:
+ * - David Burhans
+ */
+#endregion
+
+using System;
+using System.Collections.Specialized;
+using System.IO;
+using System.Text;
+using WebSocketSharp.Net;
+
+namespace WebSocketSharp
+{
+ internal class HttpRequest : HttpBase
+ {
+ #region Private Fields
+
+ private CookieCollection _cookies;
+ private string _method;
+ private string _target;
+
+ #endregion
+
+ #region Private Constructors
+
+ private HttpRequest (
+ string method,
+ string target,
+ Version version,
+ NameValueCollection headers
+ )
+ : base (version, headers)
+ {
+ _method = method;
+ _target = target;
+ }
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal HttpRequest (string method, string target)
+ : this (method, target, HttpVersion.Version11, new NameValueCollection ())
+ {
+ Headers["User-Agent"] = "websocket-sharp/1.0";
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal string RequestLine {
+ get {
+ var fmt = "{0} {1} HTTP/{2}{3}";
+
+ return String.Format (fmt, _method, _target, ProtocolVersion, CrLf);
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ public AuthenticationResponse AuthenticationResponse {
+ get {
+ var val = Headers["Authorization"];
+
+ return val != null && val.Length > 0
+ ? AuthenticationResponse.Parse (val)
+ : null;
+ }
+ }
+
+ public CookieCollection Cookies {
+ get {
+ if (_cookies == null)
+ _cookies = Headers.GetCookies (false);
+
+ return _cookies;
+ }
+ }
+
+ public string HttpMethod {
+ get {
+ return _method;
+ }
+ }
+
+ public bool IsWebSocketRequest {
+ get {
+ return _method == "GET"
+ && ProtocolVersion > HttpVersion.Version10
+ && Headers.Upgrades ("websocket");
+ }
+ }
+
+ public override string MessageHeader {
+ get {
+ return RequestLine + HeaderSection;
+ }
+ }
+
+ public string RequestTarget {
+ get {
+ return _target;
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal static HttpRequest CreateConnectRequest (Uri targetUri)
+ {
+ var fmt = "{0}:{1}";
+ var host = targetUri.DnsSafeHost;
+ var port = targetUri.Port;
+ var authority = String.Format (fmt, host, port);
+
+ var ret = new HttpRequest ("CONNECT", authority);
+
+ ret.Headers["Host"] = port != 80 ? authority : host;
+
+ return ret;
+ }
+
+ internal static HttpRequest CreateWebSocketHandshakeRequest (Uri targetUri)
+ {
+ var ret = new HttpRequest ("GET", targetUri.PathAndQuery);
+
+ var headers = ret.Headers;
+
+ var port = targetUri.Port;
+ var schm = targetUri.Scheme;
+ var isDefaultPort = (port == 80 && schm == "ws")
+ || (port == 443 && schm == "wss");
+
+ headers["Host"] = !isDefaultPort
+ ? targetUri.Authority
+ : targetUri.DnsSafeHost;
+
+ headers["Upgrade"] = "websocket";
+ headers["Connection"] = "Upgrade";
+
+ return ret;
+ }
+
+ internal HttpResponse GetResponse (Stream stream, int millisecondsTimeout)
+ {
+ WriteTo (stream);
+
+ return HttpResponse.ReadResponse (stream, millisecondsTimeout);
+ }
+
+ internal static HttpRequest Parse (string[] messageHeader)
+ {
+ var len = messageHeader.Length;
+
+ if (len == 0) {
+ var msg = "An empty request header.";
+
+ throw new ArgumentException (msg);
+ }
+
+ var rlParts = messageHeader[0].Split (new[] { ' ' }, 3);
+
+ if (rlParts.Length != 3) {
+ var msg = "It includes an invalid request line.";
+
+ throw new ArgumentException (msg);
+ }
+
+ var method = rlParts[0];
+ var target = rlParts[1];
+ var ver = rlParts[2].Substring (5).ToVersion ();
+
+ var headers = new WebHeaderCollection ();
+
+ for (var i = 1; i < len; i++)
+ headers.InternalSet (messageHeader[i], false);
+
+ return new HttpRequest (method, target, ver, headers);
+ }
+
+ internal static HttpRequest ReadRequest (
+ Stream stream,
+ int millisecondsTimeout
+ )
+ {
+ return Read (stream, Parse, millisecondsTimeout);
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public void SetCookies (CookieCollection cookies)
+ {
+ if (cookies == null || cookies.Count == 0)
+ return;
+
+ var buff = new StringBuilder (64);
+
+ foreach (var cookie in cookies.Sorted) {
+ if (cookie.Expired)
+ continue;
+
+ buff.AppendFormat ("{0}; ", cookie);
+ }
+
+ var len = buff.Length;
+
+ if (len <= 2)
+ return;
+
+ buff.Length = len - 2;
+
+ Headers["Cookie"] = buff.ToString ();
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/HttpRequest.cs.meta b/Assets/External/websocket-sharp/HttpRequest.cs.meta
new file mode 100644
index 00000000..4f6ac424
--- /dev/null
+++ b/Assets/External/websocket-sharp/HttpRequest.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: f38d7bd29af589740a8ee756fd3dce99
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/HttpResponse.cs b/Assets/External/websocket-sharp/HttpResponse.cs
new file mode 100644
index 00000000..fb2f9d31
--- /dev/null
+++ b/Assets/External/websocket-sharp/HttpResponse.cs
@@ -0,0 +1,274 @@
+#region License
+/*
+ * HttpResponse.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2024 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.Specialized;
+using System.IO;
+using System.Text;
+using WebSocketSharp.Net;
+
+namespace WebSocketSharp
+{
+ internal class HttpResponse : HttpBase
+ {
+ #region Private Fields
+
+ private int _code;
+ private string _reason;
+
+ #endregion
+
+ #region Private Constructors
+
+ private HttpResponse (
+ int code,
+ string reason,
+ Version version,
+ NameValueCollection headers
+ )
+ : base (version, headers)
+ {
+ _code = code;
+ _reason = reason;
+ }
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal HttpResponse (int code)
+ : this (code, code.GetStatusDescription ())
+ {
+ }
+
+ internal HttpResponse (HttpStatusCode code)
+ : this ((int) code)
+ {
+ }
+
+ internal HttpResponse (int code, string reason)
+ : this (
+ code,
+ reason,
+ HttpVersion.Version11,
+ new NameValueCollection ()
+ )
+ {
+ Headers["Server"] = "websocket-sharp/1.0";
+ }
+
+ internal HttpResponse (HttpStatusCode code, string reason)
+ : this ((int) code, reason)
+ {
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal string StatusLine {
+ get {
+ return _reason != null
+ ? String.Format (
+ "HTTP/{0} {1} {2}{3}",
+ ProtocolVersion,
+ _code,
+ _reason,
+ CrLf
+ )
+ : String.Format (
+ "HTTP/{0} {1}{2}",
+ ProtocolVersion,
+ _code,
+ CrLf
+ );
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ public bool CloseConnection {
+ get {
+ var compType = StringComparison.OrdinalIgnoreCase;
+
+ return Headers.Contains ("Connection", "close", compType);
+ }
+ }
+
+ public CookieCollection Cookies {
+ get {
+ return Headers.GetCookies (true);
+ }
+ }
+
+ public bool IsProxyAuthenticationRequired {
+ get {
+ return _code == 407;
+ }
+ }
+
+ public bool IsRedirect {
+ get {
+ return _code == 301 || _code == 302;
+ }
+ }
+
+ public bool IsSuccess {
+ get {
+ return _code >= 200 && _code <= 299;
+ }
+ }
+
+ public bool IsUnauthorized {
+ get {
+ return _code == 401;
+ }
+ }
+
+ public bool IsWebSocketResponse {
+ get {
+ return ProtocolVersion > HttpVersion.Version10
+ && _code == 101
+ && Headers.Upgrades ("websocket");
+ }
+ }
+
+ public override string MessageHeader {
+ get {
+ return StatusLine + HeaderSection;
+ }
+ }
+
+ public string Reason {
+ get {
+ return _reason;
+ }
+ }
+
+ public int StatusCode {
+ get {
+ return _code;
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal static HttpResponse CreateCloseResponse (HttpStatusCode code)
+ {
+ var ret = new HttpResponse (code);
+
+ ret.Headers["Connection"] = "close";
+
+ return ret;
+ }
+
+ internal static HttpResponse CreateUnauthorizedResponse (string challenge)
+ {
+ var ret = new HttpResponse (HttpStatusCode.Unauthorized);
+
+ ret.Headers["WWW-Authenticate"] = challenge;
+
+ return ret;
+ }
+
+ internal static HttpResponse CreateWebSocketHandshakeResponse ()
+ {
+ var ret = new HttpResponse (HttpStatusCode.SwitchingProtocols);
+
+ var headers = ret.Headers;
+
+ headers["Upgrade"] = "websocket";
+ headers["Connection"] = "Upgrade";
+
+ return ret;
+ }
+
+ internal static HttpResponse Parse (string[] messageHeader)
+ {
+ var len = messageHeader.Length;
+
+ if (len == 0) {
+ var msg = "An empty response header.";
+
+ throw new ArgumentException (msg);
+ }
+
+ var slParts = messageHeader[0].Split (new[] { ' ' }, 3);
+ var plen = slParts.Length;
+
+ if (plen < 2) {
+ var msg = "It includes an invalid status line.";
+
+ throw new ArgumentException (msg);
+ }
+
+ var code = slParts[1].ToInt32 ();
+ var reason = plen == 3 ? slParts[2] : null;
+ var ver = slParts[0].Substring (5).ToVersion ();
+
+ var headers = new WebHeaderCollection ();
+
+ for (var i = 1; i < len; i++)
+ headers.InternalSet (messageHeader[i], true);
+
+ return new HttpResponse (code, reason, ver, headers);
+ }
+
+ internal static HttpResponse ReadResponse (
+ Stream stream,
+ int millisecondsTimeout
+ )
+ {
+ return Read (stream, Parse, millisecondsTimeout);
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public void SetCookies (CookieCollection cookies)
+ {
+ if (cookies == null || cookies.Count == 0)
+ return;
+
+ var headers = Headers;
+
+ foreach (var cookie in cookies.Sorted) {
+ var val = cookie.ToResponseString ();
+
+ headers.Add ("Set-Cookie", val);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/HttpResponse.cs.meta b/Assets/External/websocket-sharp/HttpResponse.cs.meta
new file mode 100644
index 00000000..8dfa4717
--- /dev/null
+++ b/Assets/External/websocket-sharp/HttpResponse.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: f3f90e85d078fa341af27ca4d173c389
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/LogData.cs b/Assets/External/websocket-sharp/LogData.cs
new file mode 100644
index 00000000..bb3492a9
--- /dev/null
+++ b/Assets/External/websocket-sharp/LogData.cs
@@ -0,0 +1,159 @@
+#region License
+/*
+ * LogData.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2013-2024 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.Diagnostics;
+using System.Text;
+
+namespace WebSocketSharp
+{
+ ///
+ /// Represents a log data used by the class.
+ ///
+ public class LogData
+ {
+ #region Private Fields
+
+ private StackFrame _caller;
+ private DateTime _date;
+ private LogLevel _level;
+ private string _message;
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal LogData (LogLevel level, StackFrame caller, string message)
+ {
+ _level = level;
+ _caller = caller;
+ _message = message ?? String.Empty;
+
+ _date = DateTime.Now;
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the information of the logging method caller.
+ ///
+ ///
+ /// A that provides the information of
+ /// the logging method caller.
+ ///
+ public StackFrame Caller {
+ get {
+ return _caller;
+ }
+ }
+
+ ///
+ /// Gets the date and time when the log data was created.
+ ///
+ ///
+ /// A that represents the date and time when
+ /// the log data was created.
+ ///
+ public DateTime Date {
+ get {
+ return _date;
+ }
+ }
+
+ ///
+ /// Gets the logging level of the log data.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It represents the logging level of the log data.
+ ///
+ ///
+ public LogLevel Level {
+ get {
+ return _level;
+ }
+ }
+
+ ///
+ /// Gets the message of the log data.
+ ///
+ ///
+ /// A that represents the message of the log data.
+ ///
+ public string Message {
+ get {
+ return _message;
+ }
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Returns a string that represents the current instance.
+ ///
+ ///
+ /// A that represents the current instance.
+ ///
+ public override string ToString ()
+ {
+ var date = String.Format ("[{0}]", _date);
+ var level = String.Format ("{0,-5}", _level.ToString ().ToUpper ());
+
+ var method = _caller.GetMethod ();
+ var type = method.DeclaringType;
+#if DEBUG
+ var num = _caller.GetFileLineNumber ();
+ var caller = String.Format ("{0}.{1}:{2}", type.Name, method.Name, num);
+#else
+ var caller = String.Format ("{0}.{1}", type.Name, method.Name);
+#endif
+ var msgs = _message.Replace ("\r\n", "\n").TrimEnd ('\n').Split ('\n');
+
+ if (msgs.Length <= 1)
+ return String.Format ("{0} {1} {2} {3}", date, level, caller, _message);
+
+ var buff = new StringBuilder (64);
+
+ buff.AppendFormat ("{0} {1} {2}\n\n", date, level, caller);
+
+ foreach (var msg in msgs)
+ buff.AppendFormat (" {0}\n", msg);
+
+ return buff.ToString ();
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/LogData.cs.meta b/Assets/External/websocket-sharp/LogData.cs.meta
new file mode 100644
index 00000000..c8e27a29
--- /dev/null
+++ b/Assets/External/websocket-sharp/LogData.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 7baa1e0e1d5589a4cb5d7990896ccf5e
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/LogLevel.cs b/Assets/External/websocket-sharp/LogLevel.cs
new file mode 100644
index 00000000..5ff1d8fe
--- /dev/null
+++ b/Assets/External/websocket-sharp/LogLevel.cs
@@ -0,0 +1,67 @@
+#region License
+/*
+ * LogLevel.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2013-2022 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;
+
+namespace WebSocketSharp
+{
+ ///
+ /// Specifies the logging level.
+ ///
+ public enum LogLevel
+ {
+ ///
+ /// Specifies the bottom logging level.
+ ///
+ Trace,
+ ///
+ /// Specifies the 2nd logging level from the bottom.
+ ///
+ Debug,
+ ///
+ /// Specifies the 3rd logging level from the bottom.
+ ///
+ Info,
+ ///
+ /// Specifies the 3rd logging level from the top.
+ ///
+ Warn,
+ ///
+ /// Specifies the 2nd logging level from the top.
+ ///
+ Error,
+ ///
+ /// Specifies the top logging level.
+ ///
+ Fatal,
+ ///
+ /// Specifies not to output logs.
+ ///
+ None
+ }
+}
diff --git a/Assets/External/websocket-sharp/LogLevel.cs.meta b/Assets/External/websocket-sharp/LogLevel.cs.meta
new file mode 100644
index 00000000..96b38137
--- /dev/null
+++ b/Assets/External/websocket-sharp/LogLevel.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 8684c3e3443c55b47b0a2ffe1d14909c
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Logger.cs b/Assets/External/websocket-sharp/Logger.cs
new file mode 100644
index 00000000..a280ce43
--- /dev/null
+++ b/Assets/External/websocket-sharp/Logger.cs
@@ -0,0 +1,345 @@
+#region License
+/*
+ * Logger.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2013-2024 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.Diagnostics;
+using System.IO;
+
+namespace WebSocketSharp
+{
+ ///
+ /// Provides a set of methods and properties for logging.
+ ///
+ ///
+ ///
+ /// If you output a log with lower than the current logging level,
+ /// it cannot be outputted.
+ ///
+ ///
+ /// The default output method writes a log to the standard output
+ /// stream and the text file if it has a valid path.
+ ///
+ ///
+ /// If you would like to use the custom output method, you should
+ /// specify it with the constructor or the
+ /// property.
+ ///
+ ///
+ public class Logger
+ {
+ #region Private Fields
+
+ private volatile string _file;
+ private volatile LogLevel _level;
+ private Action _output;
+ private object _sync;
+
+ #endregion
+
+ #region Public Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// This constructor initializes the logging level with the Error level.
+ ///
+ public Logger ()
+ : this (LogLevel.Error, null, null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with
+ /// the specified logging level.
+ ///
+ ///
+ /// One of the enum values that specifies
+ /// the logging level.
+ ///
+ public Logger (LogLevel level)
+ : this (level, null, null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with
+ /// the specified logging level, path to the log file, and delegate
+ /// used to output a log.
+ ///
+ ///
+ /// One of the enum values that specifies
+ /// the logging level.
+ ///
+ ///
+ /// A that specifies the path to the log file.
+ ///
+ ///
+ /// An that specifies
+ /// the delegate used to output a log.
+ ///
+ public Logger (LogLevel level, string file, Action output)
+ {
+ _level = level;
+ _file = file;
+ _output = output ?? defaultOutput;
+
+ _sync = new object ();
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets or sets the path to the log file.
+ ///
+ ///
+ /// A that represents the path to the log file if any.
+ ///
+ public string File {
+ get {
+ return _file;
+ }
+
+ set {
+ lock (_sync)
+ _file = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the current logging level.
+ ///
+ ///
+ /// A log with lower than the value of this property cannot be outputted.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It represents the current logging level.
+ ///
+ ///
+ public LogLevel Level {
+ get {
+ return _level;
+ }
+
+ set {
+ lock (_sync)
+ _level = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the delegate used to output a log.
+ ///
+ ///
+ ///
+ /// An delegate.
+ ///
+ ///
+ /// It represents the delegate called when the logger outputs a log.
+ ///
+ ///
+ /// The string parameter passed to the delegate is the value of
+ /// the property.
+ ///
+ ///
+ /// If the value to set is , the default
+ /// output method is set.
+ ///
+ ///
+ public Action Output {
+ get {
+ return _output;
+ }
+
+ set {
+ lock (_sync)
+ _output = value ?? defaultOutput;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private static void defaultOutput (LogData data, string path)
+ {
+ var val = data.ToString ();
+
+ Console.WriteLine (val);
+
+ if (path != null && path.Length > 0)
+ writeToFile (val, path);
+ }
+
+ private void output (string message, LogLevel level)
+ {
+ lock (_sync) {
+ if (_level > level)
+ return;
+
+ try {
+ var data = new LogData (level, new StackFrame (2, true), message);
+
+ _output (data, _file);
+ }
+ catch (Exception ex) {
+ var data = new LogData (
+ LogLevel.Fatal,
+ new StackFrame (0, true),
+ ex.Message
+ );
+
+ Console.WriteLine (data.ToString ());
+ }
+ }
+ }
+
+ private static void writeToFile (string value, string path)
+ {
+ using (var writer = new StreamWriter (path, true))
+ using (var syncWriter = TextWriter.Synchronized (writer))
+ syncWriter.WriteLine (value);
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Outputs the specified message as a log with the Debug level.
+ ///
+ ///
+ /// If the current logging level is higher than the Debug level,
+ /// this method does not output the message as a log.
+ ///
+ ///
+ /// A that specifies the message to output.
+ ///
+ public void Debug (string message)
+ {
+ if (_level > LogLevel.Debug)
+ return;
+
+ output (message, LogLevel.Debug);
+ }
+
+ ///
+ /// Outputs the specified message as a log with the Error level.
+ ///
+ ///
+ /// If the current logging level is higher than the Error level,
+ /// this method does not output the message as a log.
+ ///
+ ///
+ /// A that specifies the message to output.
+ ///
+ public void Error (string message)
+ {
+ if (_level > LogLevel.Error)
+ return;
+
+ output (message, LogLevel.Error);
+ }
+
+ ///
+ /// Outputs the specified message as a log with the Fatal level.
+ ///
+ ///
+ /// A that specifies the message to output.
+ ///
+ public void Fatal (string message)
+ {
+ if (_level > LogLevel.Fatal)
+ return;
+
+ output (message, LogLevel.Fatal);
+ }
+
+ ///
+ /// Outputs the specified message as a log with the Info level.
+ ///
+ ///
+ /// If the current logging level is higher than the Info level,
+ /// this method does not output the message as a log.
+ ///
+ ///
+ /// A that specifies the message to output.
+ ///
+ public void Info (string message)
+ {
+ if (_level > LogLevel.Info)
+ return;
+
+ output (message, LogLevel.Info);
+ }
+
+ ///
+ /// Outputs the specified message as a log with the Trace level.
+ ///
+ ///
+ /// If the current logging level is higher than the Trace level,
+ /// this method does not output the message as a log.
+ ///
+ ///
+ /// A that specifies the message to output.
+ ///
+ public void Trace (string message)
+ {
+ if (_level > LogLevel.Trace)
+ return;
+
+ output (message, LogLevel.Trace);
+ }
+
+ ///
+ /// Outputs the specified message as a log with the Warn level.
+ ///
+ ///
+ /// If the current logging level is higher than the Warn level,
+ /// this method does not output the message as a log.
+ ///
+ ///
+ /// A that specifies the message to output.
+ ///
+ public void Warn (string message)
+ {
+ if (_level > LogLevel.Warn)
+ return;
+
+ output (message, LogLevel.Warn);
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Logger.cs.meta b/Assets/External/websocket-sharp/Logger.cs.meta
new file mode 100644
index 00000000..3194b864
--- /dev/null
+++ b/Assets/External/websocket-sharp/Logger.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 3af5c1735c57c5944acd190ccf689365
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Mask.cs b/Assets/External/websocket-sharp/Mask.cs
new file mode 100644
index 00000000..2958f5a8
--- /dev/null
+++ b/Assets/External/websocket-sharp/Mask.cs
@@ -0,0 +1,52 @@
+#region License
+/*
+ * Mask.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2025 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;
+
+namespace WebSocketSharp
+{
+ ///
+ /// Indicates whether the payload data of a WebSocket frame is masked.
+ ///
+ ///
+ /// The values of this enumeration are defined in
+ ///
+ /// Section 5.2 of RFC 6455.
+ ///
+ internal enum Mask
+ {
+ ///
+ /// Equivalent to numeric value 0. Indicates not masked.
+ ///
+ Off = 0x0,
+ ///
+ /// Equivalent to numeric value 1. Indicates masked.
+ ///
+ On = 0x1
+ }
+}
diff --git a/Assets/External/websocket-sharp/Mask.cs.meta b/Assets/External/websocket-sharp/Mask.cs.meta
new file mode 100644
index 00000000..a7399cb5
--- /dev/null
+++ b/Assets/External/websocket-sharp/Mask.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: e96ce8210fac01d43aab00d3ee6e24d9
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/MessageEventArgs.cs b/Assets/External/websocket-sharp/MessageEventArgs.cs
new file mode 100644
index 00000000..63add90f
--- /dev/null
+++ b/Assets/External/websocket-sharp/MessageEventArgs.cs
@@ -0,0 +1,192 @@
+#region License
+/*
+ * MessageEventArgs.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2022 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;
+
+namespace WebSocketSharp
+{
+ ///
+ /// Represents the event data for the event.
+ ///
+ ///
+ ///
+ /// The message event occurs when the interface
+ /// receives a message or a ping if the
+ /// property is set to true.
+ ///
+ ///
+ /// If you would like to get the message data, you should access
+ /// the or property.
+ ///
+ ///
+ public class MessageEventArgs : EventArgs
+ {
+ #region Private Fields
+
+ private string _data;
+ private bool _dataSet;
+ private Opcode _opcode;
+ private byte[] _rawData;
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal MessageEventArgs (WebSocketFrame frame)
+ {
+ _opcode = frame.Opcode;
+ _rawData = frame.PayloadData.ApplicationData;
+ }
+
+ internal MessageEventArgs (Opcode opcode, byte[] rawData)
+ {
+ if ((ulong) rawData.LongLength > PayloadData.MaxLength)
+ throw new WebSocketException (CloseStatusCode.TooBig);
+
+ _opcode = opcode;
+ _rawData = rawData;
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ ///
+ /// Gets the opcode for the message.
+ ///
+ ///
+ /// , ,
+ /// or .
+ ///
+ internal Opcode Opcode {
+ get {
+ return _opcode;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the message data as a .
+ ///
+ ///
+ ///
+ /// A that represents the message data
+ /// if the message type is text or ping.
+ ///
+ ///
+ /// if the message type is binary or
+ /// the message data could not be UTF-8-decoded.
+ ///
+ ///
+ public string Data {
+ get {
+ setData ();
+
+ return _data;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the message type is binary.
+ ///
+ ///
+ /// true if the message type is binary; otherwise, false.
+ ///
+ public bool IsBinary {
+ get {
+ return _opcode == Opcode.Binary;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the message type is ping.
+ ///
+ ///
+ /// true if the message type is ping; otherwise, false.
+ ///
+ public bool IsPing {
+ get {
+ return _opcode == Opcode.Ping;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the message type is text.
+ ///
+ ///
+ /// true if the message type is text; otherwise, false.
+ ///
+ public bool IsText {
+ get {
+ return _opcode == Opcode.Text;
+ }
+ }
+
+ ///
+ /// Gets the message data as an array of .
+ ///
+ ///
+ /// An array of that represents the message data.
+ ///
+ public byte[] RawData {
+ get {
+ setData ();
+
+ return _rawData;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private void setData ()
+ {
+ if (_dataSet)
+ return;
+
+ if (_opcode == Opcode.Binary) {
+ _dataSet = true;
+
+ return;
+ }
+
+ string data;
+
+ if (_rawData.TryGetUTF8DecodedString (out data))
+ _data = data;
+
+ _dataSet = true;
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/MessageEventArgs.cs.meta b/Assets/External/websocket-sharp/MessageEventArgs.cs.meta
new file mode 100644
index 00000000..f87be3b6
--- /dev/null
+++ b/Assets/External/websocket-sharp/MessageEventArgs.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: b9ecfe7845aca0f47b82eb3bf34eeebc
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net.meta b/Assets/External/websocket-sharp/Net.meta
new file mode 100644
index 00000000..385160bc
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: c24f6cb4d39821c41a0119a5c2052211
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/External/websocket-sharp/Net/AuthenticationChallenge.cs b/Assets/External/websocket-sharp/Net/AuthenticationChallenge.cs
new file mode 100644
index 00000000..72674080
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/AuthenticationChallenge.cs
@@ -0,0 +1,280 @@
+#region License
+/*
+ * AuthenticationChallenge.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2013-2024 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.Specialized;
+using System.Text;
+
+namespace WebSocketSharp.Net
+{
+ internal class AuthenticationChallenge
+ {
+ #region Private Fields
+
+ private NameValueCollection _parameters;
+ private AuthenticationSchemes _scheme;
+
+ #endregion
+
+ #region Private Constructors
+
+ private AuthenticationChallenge (
+ AuthenticationSchemes scheme,
+ NameValueCollection parameters
+ )
+ {
+ _scheme = scheme;
+ _parameters = parameters;
+ }
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal AuthenticationChallenge (
+ AuthenticationSchemes scheme,
+ string realm
+ )
+ : this (scheme, new NameValueCollection ())
+ {
+ _parameters["realm"] = realm;
+
+ if (scheme == AuthenticationSchemes.Digest) {
+ _parameters["nonce"] = CreateNonceValue ();
+ _parameters["algorithm"] = "MD5";
+ _parameters["qop"] = "auth";
+ }
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal NameValueCollection Parameters {
+ get {
+ return _parameters;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ public string Algorithm {
+ get {
+ return _parameters["algorithm"];
+ }
+ }
+
+ public string Domain {
+ get {
+ return _parameters["domain"];
+ }
+ }
+
+ public string Nonce {
+ get {
+ return _parameters["nonce"];
+ }
+ }
+
+ public string Opaque {
+ get {
+ return _parameters["opaque"];
+ }
+ }
+
+ public string Qop {
+ get {
+ return _parameters["qop"];
+ }
+ }
+
+ public string Realm {
+ get {
+ return _parameters["realm"];
+ }
+ }
+
+ public AuthenticationSchemes Scheme {
+ get {
+ return _scheme;
+ }
+ }
+
+ public string Stale {
+ get {
+ return _parameters["stale"];
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal static AuthenticationChallenge CreateBasicChallenge (string realm)
+ {
+ return new AuthenticationChallenge (AuthenticationSchemes.Basic, realm);
+ }
+
+ internal static AuthenticationChallenge CreateDigestChallenge (string realm)
+ {
+ return new AuthenticationChallenge (AuthenticationSchemes.Digest, realm);
+ }
+
+ internal static string CreateNonceValue ()
+ {
+ var rand = new Random ();
+ var bytes = new byte[16];
+
+ rand.NextBytes (bytes);
+
+ var buff = new StringBuilder (32);
+
+ foreach (var b in bytes)
+ buff.Append (b.ToString ("x2"));
+
+ return buff.ToString ();
+ }
+
+ internal static AuthenticationChallenge Parse (string value)
+ {
+ var chal = value.Split (new[] { ' ' }, 2);
+
+ if (chal.Length != 2)
+ return null;
+
+ var schm = chal[0].ToLower ();
+
+ if (schm == "basic") {
+ var parameters = ParseParameters (chal[1]);
+
+ return new AuthenticationChallenge (
+ AuthenticationSchemes.Basic,
+ parameters
+ );
+ }
+
+ if (schm == "digest") {
+ var parameters = ParseParameters (chal[1]);
+
+ return new AuthenticationChallenge (
+ AuthenticationSchemes.Digest,
+ parameters
+ );
+ }
+
+ return null;
+ }
+
+ internal static NameValueCollection ParseParameters (string value)
+ {
+ var ret = new NameValueCollection ();
+
+ foreach (var param in value.SplitHeaderValue (',')) {
+ var i = param.IndexOf ('=');
+
+ var name = i > 0 ? param.Substring (0, i).Trim () : null;
+ var val = i < 0
+ ? param.Trim ().Trim ('"')
+ : i < param.Length - 1
+ ? param.Substring (i + 1).Trim ().Trim ('"')
+ : String.Empty;
+
+ ret.Add (name, val);
+ }
+
+ return ret;
+ }
+
+ internal string ToBasicString ()
+ {
+ return String.Format ("Basic realm=\"{0}\"", _parameters["realm"]);
+ }
+
+ internal string ToDigestString ()
+ {
+ var buff = new StringBuilder (128);
+
+ var domain = _parameters["domain"];
+ var realm = _parameters["realm"];
+ var nonce = _parameters["nonce"];
+
+ if (domain != null) {
+ buff.AppendFormat (
+ "Digest realm=\"{0}\", domain=\"{1}\", nonce=\"{2}\"",
+ realm,
+ domain,
+ nonce
+ );
+ }
+ else {
+ buff.AppendFormat ("Digest realm=\"{0}\", nonce=\"{1}\"", realm, nonce);
+ }
+
+ var opaque = _parameters["opaque"];
+
+ if (opaque != null)
+ buff.AppendFormat (", opaque=\"{0}\"", opaque);
+
+ var stale = _parameters["stale"];
+
+ if (stale != null)
+ buff.AppendFormat (", stale={0}", stale);
+
+ var algo = _parameters["algorithm"];
+
+ if (algo != null)
+ buff.AppendFormat (", algorithm={0}", algo);
+
+ var qop = _parameters["qop"];
+
+ if (qop != null)
+ buff.AppendFormat (", qop=\"{0}\"", qop);
+
+ return buff.ToString ();
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public override string ToString ()
+ {
+ if (_scheme == AuthenticationSchemes.Basic)
+ return ToBasicString ();
+
+ if (_scheme == AuthenticationSchemes.Digest)
+ return ToDigestString ();
+
+ return String.Empty;
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/AuthenticationChallenge.cs.meta b/Assets/External/websocket-sharp/Net/AuthenticationChallenge.cs.meta
new file mode 100644
index 00000000..db5ab960
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/AuthenticationChallenge.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 84c0bed85decb0b479b4e5b420f1285a
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/AuthenticationResponse.cs b/Assets/External/websocket-sharp/Net/AuthenticationResponse.cs
new file mode 100644
index 00000000..28fbd223
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/AuthenticationResponse.cs
@@ -0,0 +1,464 @@
+#region License
+/*
+ * AuthenticationResponse.cs
+ *
+ * The ParseBasicCredentials method is derived from HttpListenerContext.cs
+ * (System.Net) of Mono (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2013-2024 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.Specialized;
+using System.Security.Cryptography;
+using System.Security.Principal;
+using System.Text;
+
+namespace WebSocketSharp.Net
+{
+ internal class AuthenticationResponse
+ {
+ #region Private Fields
+
+ private uint _nonceCount;
+ private NameValueCollection _parameters;
+ private AuthenticationSchemes _scheme;
+
+ #endregion
+
+ #region Private Constructors
+
+ private AuthenticationResponse (
+ AuthenticationSchemes scheme,
+ NameValueCollection parameters
+ )
+ {
+ _scheme = scheme;
+ _parameters = parameters;
+ }
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal AuthenticationResponse (NetworkCredential credentials)
+ : this (
+ AuthenticationSchemes.Basic,
+ new NameValueCollection (),
+ credentials,
+ 0
+ )
+ {
+ }
+
+ internal AuthenticationResponse (
+ AuthenticationChallenge challenge,
+ NetworkCredential credentials,
+ uint nonceCount
+ )
+ : this (challenge.Scheme, challenge.Parameters, credentials, nonceCount)
+ {
+ }
+
+ internal AuthenticationResponse (
+ AuthenticationSchemes scheme,
+ NameValueCollection parameters,
+ NetworkCredential credentials,
+ uint nonceCount
+ )
+ : this (scheme, parameters)
+ {
+ _parameters["username"] = credentials.Username;
+ _parameters["password"] = credentials.Password;
+ _parameters["uri"] = credentials.Domain;
+ _nonceCount = nonceCount;
+
+ if (scheme == AuthenticationSchemes.Digest)
+ initAsDigest ();
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal uint NonceCount {
+ get {
+ return _nonceCount < UInt32.MaxValue ? _nonceCount : 0;
+ }
+ }
+
+ internal NameValueCollection Parameters {
+ get {
+ return _parameters;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ public string Algorithm {
+ get {
+ return _parameters["algorithm"];
+ }
+ }
+
+ public string Cnonce {
+ get {
+ return _parameters["cnonce"];
+ }
+ }
+
+ public string Nc {
+ get {
+ return _parameters["nc"];
+ }
+ }
+
+ public string Nonce {
+ get {
+ return _parameters["nonce"];
+ }
+ }
+
+ public string Opaque {
+ get {
+ return _parameters["opaque"];
+ }
+ }
+
+ public string Password {
+ get {
+ return _parameters["password"];
+ }
+ }
+
+ public string Qop {
+ get {
+ return _parameters["qop"];
+ }
+ }
+
+ public string Realm {
+ get {
+ return _parameters["realm"];
+ }
+ }
+
+ public string Response {
+ get {
+ return _parameters["response"];
+ }
+ }
+
+ public AuthenticationSchemes Scheme {
+ get {
+ return _scheme;
+ }
+ }
+
+ public string Uri {
+ get {
+ return _parameters["uri"];
+ }
+ }
+
+ public string UserName {
+ get {
+ return _parameters["username"];
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private static string createA1 (
+ string username,
+ string password,
+ string realm
+ )
+ {
+ return String.Format ("{0}:{1}:{2}", username, realm, password);
+ }
+
+ private static string createA1 (
+ string username,
+ string password,
+ string realm,
+ string nonce,
+ string cnonce
+ )
+ {
+ var a1 = createA1 (username, password, realm);
+
+ return String.Format ("{0}:{1}:{2}", hash (a1), nonce, cnonce);
+ }
+
+ private static string createA2 (string method, string uri)
+ {
+ return String.Format ("{0}:{1}", method, uri);
+ }
+
+ private static string createA2 (string method, string uri, string entity)
+ {
+ return String.Format ("{0}:{1}:{2}", method, uri, hash (entity));
+ }
+
+ private static string hash (string value)
+ {
+ var buff = new StringBuilder (64);
+
+ var md5 = MD5.Create ();
+ var bytes = Encoding.UTF8.GetBytes (value);
+ var res = md5.ComputeHash (bytes);
+
+ foreach (var b in res)
+ buff.Append (b.ToString ("x2"));
+
+ return buff.ToString ();
+ }
+
+ private void initAsDigest ()
+ {
+ var qops = _parameters["qop"];
+
+ if (qops != null) {
+ var hasAuth = qops.Split (',').Contains (
+ qop => qop.Trim ().ToLower () == "auth"
+ );
+
+ if (hasAuth) {
+ _parameters["qop"] = "auth";
+ _parameters["cnonce"] = AuthenticationChallenge.CreateNonceValue ();
+ _parameters["nc"] = String.Format ("{0:x8}", ++_nonceCount);
+ }
+ else {
+ _parameters["qop"] = null;
+ }
+ }
+
+ _parameters["method"] = "GET";
+ _parameters["response"] = CreateRequestDigest (_parameters);
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal static string CreateRequestDigest (NameValueCollection parameters)
+ {
+ var uname = parameters["username"];
+ var passwd = parameters["password"];
+ var realm = parameters["realm"];
+ var nonce = parameters["nonce"];
+ var uri = parameters["uri"];
+ var algo = parameters["algorithm"];
+ var qop = parameters["qop"];
+ var cnonce = parameters["cnonce"];
+ var nc = parameters["nc"];
+ var method = parameters["method"];
+
+ var a1 = algo != null && algo.ToLower () == "md5-sess"
+ ? createA1 (uname, passwd, realm, nonce, cnonce)
+ : createA1 (uname, passwd, realm);
+
+ var a2 = qop != null && qop.ToLower () == "auth-int"
+ ? createA2 (method, uri, parameters["entity"])
+ : createA2 (method, uri);
+
+ var secret = hash (a1);
+ var data = qop != null
+ ? String.Format (
+ "{0}:{1}:{2}:{3}:{4}",
+ nonce,
+ nc,
+ cnonce,
+ qop,
+ hash (a2)
+ )
+ : String.Format ("{0}:{1}", nonce, hash (a2));
+
+ var keyed = String.Format ("{0}:{1}", secret, data);
+
+ return hash (keyed);
+ }
+
+ internal static AuthenticationResponse Parse (string value)
+ {
+ try {
+ var cred = value.Split (new[] { ' ' }, 2);
+
+ if (cred.Length != 2)
+ return null;
+
+ var schm = cred[0].ToLower ();
+
+ if (schm == "basic") {
+ var parameters = ParseBasicCredentials (cred[1]);
+
+ return new AuthenticationResponse (
+ AuthenticationSchemes.Basic,
+ parameters
+ );
+ }
+
+ if (schm == "digest") {
+ var parameters = AuthenticationChallenge.ParseParameters (cred[1]);
+
+ return new AuthenticationResponse (
+ AuthenticationSchemes.Digest,
+ parameters
+ );
+ }
+
+ return null;
+ }
+ catch {
+ return null;
+ }
+ }
+
+ internal static NameValueCollection ParseBasicCredentials (string value)
+ {
+ var ret = new NameValueCollection ();
+
+ // Decode the basic-credentials (a Base64 encoded string).
+
+ var bytes = Convert.FromBase64String (value);
+ var userPass = Encoding.UTF8.GetString (bytes);
+
+ // The format is [\]:.
+
+ var idx = userPass.IndexOf (':');
+ var uname = userPass.Substring (0, idx);
+ var passwd = idx < userPass.Length - 1
+ ? userPass.Substring (idx + 1)
+ : String.Empty;
+
+ // Check if exists.
+
+ idx = uname.IndexOf ('\\');
+
+ if (idx > -1)
+ uname = uname.Substring (idx + 1);
+
+ ret["username"] = uname;
+ ret["password"] = passwd;
+
+ return ret;
+ }
+
+ internal string ToBasicString ()
+ {
+ var uname = _parameters["username"];
+ var passwd = _parameters["password"];
+ var userPass = String.Format ("{0}:{1}", uname, passwd);
+
+ var bytes = Encoding.UTF8.GetBytes (userPass);
+ var cred = Convert.ToBase64String (bytes);
+
+ return "Basic " + cred;
+ }
+
+ internal string ToDigestString ()
+ {
+ var buff = new StringBuilder (256);
+
+ var uname = _parameters["username"];
+ var realm = _parameters["realm"];
+ var nonce = _parameters["nonce"];
+ var uri = _parameters["uri"];
+ var res = _parameters["response"];
+
+ buff.AppendFormat (
+ "Digest username=\"{0}\", realm=\"{1}\", nonce=\"{2}\", uri=\"{3}\", response=\"{4}\"",
+ uname,
+ realm,
+ nonce,
+ uri,
+ res
+ );
+
+ var opaque = _parameters["opaque"];
+
+ if (opaque != null)
+ buff.AppendFormat (", opaque=\"{0}\"", opaque);
+
+ var algo = _parameters["algorithm"];
+
+ if (algo != null)
+ buff.AppendFormat (", algorithm={0}", algo);
+
+ var qop = _parameters["qop"];
+
+ if (qop != null) {
+ var cnonce = _parameters["cnonce"];
+ var nc = _parameters["nc"];
+
+ buff.AppendFormat (
+ ", qop={0}, cnonce=\"{1}\", nc={2}",
+ qop,
+ cnonce,
+ nc
+ );
+ }
+
+ return buff.ToString ();
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public IIdentity ToIdentity ()
+ {
+ if (_scheme == AuthenticationSchemes.Basic) {
+ var uname = _parameters["username"];
+ var passwd = _parameters["password"];
+
+ return new HttpBasicIdentity (uname, passwd);
+ }
+
+ if (_scheme == AuthenticationSchemes.Digest)
+ return new HttpDigestIdentity (_parameters);
+
+ return null;
+ }
+
+ public override string ToString ()
+ {
+ if (_scheme == AuthenticationSchemes.Basic)
+ return ToBasicString ();
+
+ if (_scheme == AuthenticationSchemes.Digest)
+ return ToDigestString ();
+
+ return String.Empty;
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/AuthenticationResponse.cs.meta b/Assets/External/websocket-sharp/Net/AuthenticationResponse.cs.meta
new file mode 100644
index 00000000..c25cc51b
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/AuthenticationResponse.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: d47f8aed92f4d1d499acfb1f91908f39
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/AuthenticationSchemes.cs b/Assets/External/websocket-sharp/Net/AuthenticationSchemes.cs
new file mode 100644
index 00000000..ab7721a1
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/AuthenticationSchemes.cs
@@ -0,0 +1,66 @@
+#region License
+/*
+ * AuthenticationSchemes.cs
+ *
+ * This code is derived from AuthenticationSchemes.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2012-2016 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
+
+#region Authors
+/*
+ * Authors:
+ * - Atsushi Enomoto
+ */
+#endregion
+
+using System;
+
+namespace WebSocketSharp.Net
+{
+ ///
+ /// Specifies the scheme for authentication.
+ ///
+ public enum AuthenticationSchemes
+ {
+ ///
+ /// No authentication is allowed.
+ ///
+ None,
+ ///
+ /// Specifies digest authentication.
+ ///
+ Digest = 1,
+ ///
+ /// Specifies basic authentication.
+ ///
+ Basic = 8,
+ ///
+ /// Specifies anonymous authentication.
+ ///
+ Anonymous = 0x8000
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/AuthenticationSchemes.cs.meta b/Assets/External/websocket-sharp/Net/AuthenticationSchemes.cs.meta
new file mode 100644
index 00000000..b8871de7
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/AuthenticationSchemes.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: d5a2c71d0b1e8a64ebd26b4420e70d3c
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/Chunk.cs b/Assets/External/websocket-sharp/Net/Chunk.cs
new file mode 100644
index 00000000..9ed28f86
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/Chunk.cs
@@ -0,0 +1,93 @@
+#region License
+/*
+ * Chunk.cs
+ *
+ * This code is derived from ChunkStream.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2003 Ximian, Inc (http://www.ximian.com)
+ * Copyright (c) 2014-2021 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+using System;
+
+namespace WebSocketSharp.Net
+{
+ internal class Chunk
+ {
+ #region Private Fields
+
+ private byte[] _data;
+ private int _offset;
+
+ #endregion
+
+ #region Public Constructors
+
+ public Chunk (byte[] data)
+ {
+ _data = data;
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ public int ReadLeft {
+ get {
+ return _data.Length - _offset;
+ }
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public int Read (byte[] buffer, int offset, int count)
+ {
+ var left = _data.Length - _offset;
+
+ if (left == 0)
+ return 0;
+
+ if (count > left)
+ count = left;
+
+ Buffer.BlockCopy (_data, _offset, buffer, offset, count);
+
+ _offset += count;
+
+ return count;
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/Chunk.cs.meta b/Assets/External/websocket-sharp/Net/Chunk.cs.meta
new file mode 100644
index 00000000..42a01b78
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/Chunk.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 14a0fcae05dbbdc4ea0cdca9bc630d0f
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/ChunkStream.cs b/Assets/External/websocket-sharp/Net/ChunkStream.cs
new file mode 100644
index 00000000..3de4374d
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/ChunkStream.cs
@@ -0,0 +1,429 @@
+#region License
+/*
+ * ChunkStream.cs
+ *
+ * This code is derived from ChunkStream.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2003 Ximian, Inc (http://www.ximian.com)
+ * Copyright (c) 2012-2023 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Text;
+
+namespace WebSocketSharp.Net
+{
+ internal class ChunkStream
+ {
+ #region Private Fields
+
+ private int _chunkRead;
+ private int _chunkSize;
+ private List _chunks;
+ private int _count;
+ private byte[] _endBuffer;
+ private bool _gotIt;
+ private WebHeaderCollection _headers;
+ private int _offset;
+ private StringBuilder _saved;
+ private bool _sawCr;
+ private InputChunkState _state;
+ private int _trailerState;
+
+ #endregion
+
+ #region Public Constructors
+
+ public ChunkStream (WebHeaderCollection headers)
+ {
+ _headers = headers;
+
+ _chunkSize = -1;
+ _chunks = new List ();
+ _saved = new StringBuilder ();
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal int Count {
+ get {
+ return _count;
+ }
+ }
+
+ internal byte[] EndBuffer {
+ get {
+ return _endBuffer;
+ }
+ }
+
+ internal int Offset {
+ get {
+ return _offset;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ public WebHeaderCollection Headers {
+ get {
+ return _headers;
+ }
+ }
+
+ public bool WantsMore {
+ get {
+ return _state < InputChunkState.End;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private int read (byte[] buffer, int offset, int count)
+ {
+ var nread = 0;
+ var cnt = _chunks.Count;
+
+ for (var i = 0; i < cnt; i++) {
+ var chunk = _chunks[i];
+
+ if (chunk == null)
+ continue;
+
+ if (chunk.ReadLeft == 0) {
+ _chunks[i] = null;
+
+ continue;
+ }
+
+ nread += chunk.Read (buffer, offset + nread, count - nread);
+
+ if (nread == count)
+ break;
+ }
+
+ return nread;
+ }
+
+ private InputChunkState seekCrLf (byte[] buffer, ref int offset, int length)
+ {
+ if (!_sawCr) {
+ if (buffer[offset++] != 13)
+ throwProtocolViolation ("CR is expected.");
+
+ _sawCr = true;
+
+ if (offset == length)
+ return InputChunkState.DataEnded;
+ }
+
+ if (buffer[offset++] != 10)
+ throwProtocolViolation ("LF is expected.");
+
+ return InputChunkState.None;
+ }
+
+ private InputChunkState setChunkSize (
+ byte[] buffer,
+ ref int offset,
+ int length
+ )
+ {
+ byte b = 0;
+
+ while (offset < length) {
+ b = buffer[offset++];
+
+ if (_sawCr) {
+ if (b != 10)
+ throwProtocolViolation ("LF is expected.");
+
+ break;
+ }
+
+ if (b == 13) {
+ _sawCr = true;
+
+ continue;
+ }
+
+ if (b == 10)
+ throwProtocolViolation ("LF is unexpected.");
+
+ if (_gotIt)
+ continue;
+
+ if (b == 32 || b == 59) { // SP or ';'
+ _gotIt = true;
+
+ continue;
+ }
+
+ _saved.Append ((char) b);
+ }
+
+ if (_saved.Length > 20)
+ throwProtocolViolation ("The chunk size is too big.");
+
+ if (b != 10)
+ return InputChunkState.None;
+
+ var s = _saved.ToString ();
+
+ try {
+ _chunkSize = Int32.Parse (s, NumberStyles.HexNumber);
+ }
+ catch {
+ throwProtocolViolation ("The chunk size cannot be parsed.");
+ }
+
+ _chunkRead = 0;
+
+ if (_chunkSize == 0) {
+ _trailerState = 2;
+
+ return InputChunkState.Trailer;
+ }
+
+ return InputChunkState.Data;
+ }
+
+ private InputChunkState setTrailer (
+ byte[] buffer,
+ ref int offset,
+ int length
+ )
+ {
+ while (offset < length) {
+ if (_trailerState == 4) // CR LF CR LF
+ break;
+
+ var b = buffer[offset++];
+
+ _saved.Append ((char) b);
+
+ if (_trailerState == 1 || _trailerState == 3) { // CR or CR LF CR
+ if (b != 10)
+ throwProtocolViolation ("LF is expected.");
+
+ _trailerState++;
+
+ continue;
+ }
+
+ if (b == 13) {
+ _trailerState++;
+
+ continue;
+ }
+
+ if (b == 10)
+ throwProtocolViolation ("LF is unexpected.");
+
+ _trailerState = 0;
+ }
+
+ var len = _saved.Length;
+
+ if (len > 4196)
+ throwProtocolViolation ("The trailer is too long.");
+
+ if (_trailerState < 4)
+ return InputChunkState.Trailer;
+
+ if (len == 2)
+ return InputChunkState.End;
+
+ _saved.Length = len - 2;
+
+ var val = _saved.ToString ();
+ var reader = new StringReader (val);
+
+ while (true) {
+ var line = reader.ReadLine ();
+
+ if (line == null || line.Length == 0)
+ break;
+
+ _headers.Add (line);
+ }
+
+ return InputChunkState.End;
+ }
+
+ private static void throwProtocolViolation (string message)
+ {
+ throw new WebException (
+ message,
+ null,
+ WebExceptionStatus.ServerProtocolViolation,
+ null
+ );
+ }
+
+ private void write (byte[] buffer, int offset, int length)
+ {
+ if (_state == InputChunkState.End)
+ throwProtocolViolation ("The chunks were ended.");
+
+ if (_state == InputChunkState.None) {
+ _state = setChunkSize (buffer, ref offset, length);
+
+ if (_state == InputChunkState.None)
+ return;
+
+ _saved.Length = 0;
+ _sawCr = false;
+ _gotIt = false;
+ }
+
+ if (_state == InputChunkState.Data) {
+ if (offset >= length)
+ return;
+
+ _state = writeData (buffer, ref offset, length);
+
+ if (_state == InputChunkState.Data)
+ return;
+ }
+
+ if (_state == InputChunkState.DataEnded) {
+ if (offset >= length)
+ return;
+
+ _state = seekCrLf (buffer, ref offset, length);
+
+ if (_state == InputChunkState.DataEnded)
+ return;
+
+ _sawCr = false;
+ }
+
+ if (_state == InputChunkState.Trailer) {
+ if (offset >= length)
+ return;
+
+ _state = setTrailer (buffer, ref offset, length);
+
+ if (_state == InputChunkState.Trailer)
+ return;
+
+ _saved.Length = 0;
+ }
+
+ if (_state == InputChunkState.End) {
+ _endBuffer = buffer;
+ _offset = offset;
+ _count = length - offset;
+
+ return;
+ }
+
+ if (offset >= length)
+ return;
+
+ write (buffer, offset, length);
+ }
+
+ private InputChunkState writeData (
+ byte[] buffer,
+ ref int offset,
+ int length
+ )
+ {
+ var cnt = length - offset;
+ var left = _chunkSize - _chunkRead;
+
+ if (cnt > left)
+ cnt = left;
+
+ var data = new byte[cnt];
+
+ Buffer.BlockCopy (buffer, offset, data, 0, cnt);
+
+ var chunk = new Chunk (data);
+
+ _chunks.Add (chunk);
+
+ offset += cnt;
+ _chunkRead += cnt;
+
+ return _chunkRead == _chunkSize
+ ? InputChunkState.DataEnded
+ : InputChunkState.Data;
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal void ResetChunkStore ()
+ {
+ _chunkRead = 0;
+ _chunkSize = -1;
+
+ _chunks.Clear ();
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public int Read (byte[] buffer, int offset, int count)
+ {
+ if (count <= 0)
+ return 0;
+
+ return read (buffer, offset, count);
+ }
+
+ public void Write (byte[] buffer, int offset, int count)
+ {
+ if (count <= 0)
+ return;
+
+ write (buffer, offset, offset + count);
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/ChunkStream.cs.meta b/Assets/External/websocket-sharp/Net/ChunkStream.cs.meta
new file mode 100644
index 00000000..b972d7eb
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/ChunkStream.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 1f64dddc3b0d0f049af97629c42dca44
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/ChunkedRequestStream.cs b/Assets/External/websocket-sharp/Net/ChunkedRequestStream.cs
new file mode 100644
index 00000000..f4a58392
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/ChunkedRequestStream.cs
@@ -0,0 +1,283 @@
+#region License
+/*
+ * ChunkedRequestStream.cs
+ *
+ * This code is derived from ChunkedInputStream.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2012-2023 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+using System;
+using System.IO;
+
+namespace WebSocketSharp.Net
+{
+ internal class ChunkedRequestStream : RequestStream
+ {
+ #region Private Fields
+
+ private static readonly int _bufferLength;
+ private HttpListenerContext _context;
+ private ChunkStream _decoder;
+ private bool _disposed;
+ private bool _noMoreData;
+
+ #endregion
+
+ #region Static Constructor
+
+ static ChunkedRequestStream ()
+ {
+ _bufferLength = 8192;
+ }
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal ChunkedRequestStream (
+ Stream innerStream,
+ byte[] initialBuffer,
+ int offset,
+ int count,
+ HttpListenerContext context
+ )
+ : base (innerStream, initialBuffer, offset, count, -1)
+ {
+ _context = context;
+
+ _decoder = new ChunkStream (
+ (WebHeaderCollection) context.Request.Headers
+ );
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal bool HasRemainingBuffer {
+ get {
+ return _decoder.Count + Count > 0;
+ }
+ }
+
+ internal byte[] RemainingBuffer {
+ get {
+ using (var buff = new MemoryStream ()) {
+ var cnt = _decoder.Count;
+
+ if (cnt > 0)
+ buff.Write (_decoder.EndBuffer, _decoder.Offset, cnt);
+
+ cnt = Count;
+
+ if (cnt > 0)
+ buff.Write (InitialBuffer, Offset, cnt);
+
+ buff.Close ();
+
+ return buff.ToArray ();
+ }
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private void onRead (IAsyncResult asyncResult)
+ {
+ var rstate = (ReadBufferState) asyncResult.AsyncState;
+ var ares = rstate.AsyncResult;
+
+ try {
+ var nread = base.EndRead (asyncResult);
+
+ _decoder.Write (ares.Buffer, ares.Offset, nread);
+
+ nread = _decoder.Read (rstate.Buffer, rstate.Offset, rstate.Count);
+
+ rstate.Offset += nread;
+ rstate.Count -= nread;
+
+ if (rstate.Count == 0 || !_decoder.WantsMore || nread == 0) {
+ _noMoreData = !_decoder.WantsMore && nread == 0;
+
+ ares.Count = rstate.InitialCount - rstate.Count;
+
+ ares.Complete ();
+
+ return;
+ }
+
+ base.BeginRead (ares.Buffer, ares.Offset, ares.Count, onRead, rstate);
+ }
+ catch (Exception ex) {
+ _context.ErrorMessage = "I/O operation aborted";
+
+ _context.SendError ();
+
+ ares.Complete (ex);
+ }
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public override IAsyncResult BeginRead (
+ byte[] buffer,
+ int offset,
+ int count,
+ AsyncCallback callback,
+ object state
+ )
+ {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (buffer == null)
+ throw new ArgumentNullException ("buffer");
+
+ if (offset < 0) {
+ var msg = "A negative value.";
+
+ throw new ArgumentOutOfRangeException ("offset", msg);
+ }
+
+ if (count < 0) {
+ var msg = "A negative value.";
+
+ throw new ArgumentOutOfRangeException ("count", msg);
+ }
+
+ var len = buffer.Length;
+
+ if (offset + count > len) {
+ var msg = "The sum of offset and count is greater than the length of buffer.";
+
+ throw new ArgumentException (msg);
+ }
+
+ var ares = new HttpStreamAsyncResult (callback, state);
+
+ if (_noMoreData) {
+ ares.Complete ();
+
+ return ares;
+ }
+
+ var nread = _decoder.Read (buffer, offset, count);
+
+ offset += nread;
+ count -= nread;
+
+ if (count == 0) {
+ ares.Count = nread;
+
+ ares.Complete ();
+
+ return ares;
+ }
+
+ if (!_decoder.WantsMore) {
+ _noMoreData = nread == 0;
+
+ ares.Count = nread;
+
+ ares.Complete ();
+
+ return ares;
+ }
+
+ ares.Buffer = new byte[_bufferLength];
+ ares.Offset = 0;
+ ares.Count = _bufferLength;
+
+ var rstate = new ReadBufferState (buffer, offset, count, ares);
+
+ rstate.InitialCount += nread;
+
+ base.BeginRead (ares.Buffer, ares.Offset, ares.Count, onRead, rstate);
+
+ return ares;
+ }
+
+ public override void Close ()
+ {
+ if (_disposed)
+ return;
+
+ base.Close ();
+
+ _disposed = true;
+ }
+
+ public override int EndRead (IAsyncResult asyncResult)
+ {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (asyncResult == null)
+ throw new ArgumentNullException ("asyncResult");
+
+ var ares = asyncResult as HttpStreamAsyncResult;
+
+ if (ares == null) {
+ var msg = "A wrong IAsyncResult instance.";
+
+ throw new ArgumentException (msg, "asyncResult");
+ }
+
+ if (!ares.IsCompleted)
+ ares.AsyncWaitHandle.WaitOne ();
+
+ if (ares.HasException) {
+ var msg = "The I/O operation has been aborted.";
+
+ throw new HttpListenerException (995, msg);
+ }
+
+ return ares.Count;
+ }
+
+ public override int Read (byte[] buffer, int offset, int count)
+ {
+ var ares = BeginRead (buffer, offset, count, null, null);
+
+ return EndRead (ares);
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/ChunkedRequestStream.cs.meta b/Assets/External/websocket-sharp/Net/ChunkedRequestStream.cs.meta
new file mode 100644
index 00000000..87030b2c
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/ChunkedRequestStream.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 1580b5e2a58b77b45b5b49739b05e897
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/ClientSslConfiguration.cs b/Assets/External/websocket-sharp/Net/ClientSslConfiguration.cs
new file mode 100644
index 00000000..33438a93
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/ClientSslConfiguration.cs
@@ -0,0 +1,311 @@
+#region License
+/*
+ * ClientSslConfiguration.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2014 liryna
+ * Copyright (c) 2014-2024 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
+
+#region Authors
+/*
+ * Authors:
+ * - Liryna
+ */
+#endregion
+
+using System;
+using System.Net.Security;
+using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
+
+namespace WebSocketSharp.Net
+{
+ ///
+ /// Stores the parameters for an instance used by
+ /// a client.
+ ///
+ public class ClientSslConfiguration
+ {
+ #region Private Fields
+
+ private bool _checkCertRevocation;
+ private LocalCertificateSelectionCallback _clientCertSelectionCallback;
+ private X509CertificateCollection _clientCerts;
+ private SslProtocols _enabledSslProtocols;
+ private RemoteCertificateValidationCallback _serverCertValidationCallback;
+ private string _targetHost;
+
+ #endregion
+
+ #region Public Constructors
+
+ ///
+ /// Initializes a new instance of the
+ /// class with the specified target host name.
+ ///
+ ///
+ /// A that specifies the name of the server that
+ /// will share a secure connection with the client.
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// is .
+ ///
+ public ClientSslConfiguration (string targetHost)
+ {
+ if (targetHost == null)
+ throw new ArgumentNullException ("targetHost");
+
+ if (targetHost.Length == 0)
+ throw new ArgumentException ("An empty string.", "targetHost");
+
+ _targetHost = targetHost;
+
+ _enabledSslProtocols = SslProtocols.None;
+ }
+
+ ///
+ /// Initializes a new instance of the
+ /// class copying from the specified configuration.
+ ///
+ ///
+ /// A from which to copy.
+ ///
+ ///
+ /// is .
+ ///
+ public ClientSslConfiguration (ClientSslConfiguration configuration)
+ {
+ if (configuration == null)
+ throw new ArgumentNullException ("configuration");
+
+ _checkCertRevocation = configuration._checkCertRevocation;
+ _clientCertSelectionCallback = configuration._clientCertSelectionCallback;
+ _clientCerts = configuration._clientCerts;
+ _enabledSslProtocols = configuration._enabledSslProtocols;
+ _serverCertValidationCallback = configuration._serverCertValidationCallback;
+ _targetHost = configuration._targetHost;
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets or sets a value indicating whether the certificate revocation
+ /// list is checked during authentication.
+ ///
+ ///
+ ///
+ /// true if the certificate revocation list is checked during
+ /// authentication; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ public bool CheckCertificateRevocation {
+ get {
+ return _checkCertRevocation;
+ }
+
+ set {
+ _checkCertRevocation = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the collection of the certificates from which to select
+ /// one to supply to the server.
+ ///
+ ///
+ ///
+ /// A that contains
+ /// the certificates from which to select.
+ ///
+ ///
+ /// if not present.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ public X509CertificateCollection ClientCertificates {
+ get {
+ return _clientCerts;
+ }
+
+ set {
+ _clientCerts = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the callback used to select the certificate to supply to
+ /// the server.
+ ///
+ ///
+ /// No certificate is supplied if the callback returns .
+ ///
+ ///
+ ///
+ /// A delegate.
+ ///
+ ///
+ /// It represents the delegate called when the client selects
+ /// the certificate.
+ ///
+ ///
+ /// The default value invokes a method that only returns
+ /// .
+ ///
+ ///
+ public LocalCertificateSelectionCallback ClientCertificateSelectionCallback {
+ get {
+ if (_clientCertSelectionCallback == null)
+ _clientCertSelectionCallback = defaultSelectClientCertificate;
+
+ return _clientCertSelectionCallback;
+ }
+
+ set {
+ _clientCertSelectionCallback = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the enabled versions of the SSL/TLS protocols.
+ ///
+ ///
+ ///
+ /// Any of the enum values.
+ ///
+ ///
+ /// It represents the enabled versions of the SSL/TLS protocols.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ public SslProtocols EnabledSslProtocols {
+ get {
+ return _enabledSslProtocols;
+ }
+
+ set {
+ _enabledSslProtocols = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the callback used to validate the certificate supplied by
+ /// the server.
+ ///
+ ///
+ /// The certificate is valid if the callback returns true.
+ ///
+ ///
+ ///
+ /// A delegate.
+ ///
+ ///
+ /// It represents the delegate called when the client validates
+ /// the certificate.
+ ///
+ ///
+ /// The default value invokes a method that only returns true.
+ ///
+ ///
+ public RemoteCertificateValidationCallback ServerCertificateValidationCallback {
+ get {
+ if (_serverCertValidationCallback == null)
+ _serverCertValidationCallback = defaultValidateServerCertificate;
+
+ return _serverCertValidationCallback;
+ }
+
+ set {
+ _serverCertValidationCallback = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the target host name.
+ ///
+ ///
+ /// A that represents the name of the server that
+ /// will share a secure connection with the client.
+ ///
+ ///
+ /// The value specified for a set operation is an empty string.
+ ///
+ ///
+ /// The value specified for a set operation is .
+ ///
+ public string TargetHost {
+ get {
+ return _targetHost;
+ }
+
+ set {
+ if (value == null)
+ throw new ArgumentNullException ("value");
+
+ if (value.Length == 0)
+ throw new ArgumentException ("An empty string.", "value");
+
+ _targetHost = value;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private static X509Certificate defaultSelectClientCertificate (
+ object sender,
+ string targetHost,
+ X509CertificateCollection clientCertificates,
+ X509Certificate serverCertificate,
+ string[] acceptableIssuers
+ )
+ {
+ return null;
+ }
+
+ private static bool defaultValidateServerCertificate (
+ object sender,
+ X509Certificate certificate,
+ X509Chain chain,
+ SslPolicyErrors sslPolicyErrors
+ )
+ {
+ return true;
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/ClientSslConfiguration.cs.meta b/Assets/External/websocket-sharp/Net/ClientSslConfiguration.cs.meta
new file mode 100644
index 00000000..2bd8464f
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/ClientSslConfiguration.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 60a0fec9d60bbcd428164ca7e1522355
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/Cookie.cs b/Assets/External/websocket-sharp/Net/Cookie.cs
new file mode 100644
index 00000000..149b5041
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/Cookie.cs
@@ -0,0 +1,1032 @@
+#region License
+/*
+ * Cookie.cs
+ *
+ * This code is derived from Cookie.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2004,2009 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2012-2024 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
+
+#region Authors
+/*
+ * Authors:
+ * - Lawrence Pit
+ * - Gonzalo Paniagua Javier
+ * - Daniel Nauck
+ * - Sebastien Pouliot
+ */
+#endregion
+
+using System;
+using System.Globalization;
+using System.Text;
+
+namespace WebSocketSharp.Net
+{
+ ///
+ /// Provides a set of methods and properties used to manage an HTTP cookie.
+ ///
+ ///
+ ///
+ /// This class refers to the following specifications:
+ ///
+ ///
+ /// -
+ ///
+ ///
+ /// Netscape specification
+ ///
+ ///
+ /// -
+ ///
+ /// RFC 2109
+ ///
+ ///
+ /// -
+ ///
+ /// RFC 2965
+ ///
+ ///
+ /// -
+ ///
+ /// RFC 6265
+ ///
+ ///
+ ///
+ ///
+ /// This class cannot be inherited.
+ ///
+ ///
+ [Serializable]
+ public sealed class Cookie
+ {
+ #region Private Fields
+
+ private string _comment;
+ private Uri _commentUri;
+ private bool _discard;
+ private string _domain;
+ private static readonly int[] _emptyPorts;
+ private DateTime _expires;
+ private bool _httpOnly;
+ private string _name;
+ private string _path;
+ private string _port;
+ private int[] _ports;
+ private static readonly char[] _reservedCharsForValue;
+ private string _sameSite;
+ private bool _secure;
+ private DateTime _timeStamp;
+ private string _value;
+ private int _version;
+
+ #endregion
+
+ #region Static Constructor
+
+ static Cookie ()
+ {
+ _emptyPorts = new int[0];
+ _reservedCharsForValue = new[] { ';', ',' };
+ }
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal Cookie ()
+ {
+ init (String.Empty, String.Empty, String.Empty, String.Empty);
+ }
+
+ #endregion
+
+ #region Public Constructors
+
+ ///
+ /// Initializes a new instance of the class with
+ /// the specified name and value.
+ ///
+ ///
+ ///
+ /// A that specifies the name of the cookie.
+ ///
+ ///
+ /// The name must be a token defined in
+ ///
+ /// RFC 2616.
+ ///
+ ///
+ ///
+ /// A that specifies the value of the cookie.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// starts with a dollar sign.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a string not enclosed in double quotes
+ /// although it contains a reserved character.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public Cookie (string name, string value)
+ : this (name, value, String.Empty, String.Empty)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with
+ /// the specified name, value, and path.
+ ///
+ ///
+ ///
+ /// A that specifies the name of the cookie.
+ ///
+ ///
+ /// The name must be a token defined in
+ ///
+ /// RFC 2616.
+ ///
+ ///
+ ///
+ /// A that specifies the value of the cookie.
+ ///
+ ///
+ /// A that specifies the value of the Path
+ /// attribute of the cookie.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// starts with a dollar sign.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a string not enclosed in double quotes
+ /// although it contains a reserved character.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public Cookie (string name, string value, string path)
+ : this (name, value, path, String.Empty)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with
+ /// the specified name, value, path, and domain.
+ ///
+ ///
+ ///
+ /// A that specifies the name of the cookie.
+ ///
+ ///
+ /// The name must be a token defined in
+ ///
+ /// RFC 2616.
+ ///
+ ///
+ ///
+ /// A that specifies the value of the cookie.
+ ///
+ ///
+ /// A that specifies the value of the Path
+ /// attribute of the cookie.
+ ///
+ ///
+ /// A that specifies the value of the Domain
+ /// attribute of the cookie.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// starts with a dollar sign.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a string not enclosed in double quotes
+ /// although it contains a reserved character.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public Cookie (string name, string value, string path, string domain)
+ {
+ if (name == null)
+ throw new ArgumentNullException ("name");
+
+ if (name.Length == 0)
+ throw new ArgumentException ("An empty string.", "name");
+
+ if (name[0] == '$') {
+ var msg = "It starts with a dollar sign.";
+
+ throw new ArgumentException (msg, "name");
+ }
+
+ if (!name.IsToken ()) {
+ var msg = "It contains an invalid character.";
+
+ throw new ArgumentException (msg, "name");
+ }
+
+ if (value == null)
+ value = String.Empty;
+
+ if (value.Contains (_reservedCharsForValue)) {
+ if (!value.IsEnclosedIn ('"')) {
+ var msg = "A string not enclosed in double quotes.";
+
+ throw new ArgumentException (msg, "value");
+ }
+ }
+
+ init (name, value, path ?? String.Empty, domain ?? String.Empty);
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal bool ExactDomain {
+ get {
+ return _domain.Length == 0 || _domain[0] != '.';
+ }
+ }
+
+ internal int MaxAge {
+ get {
+ if (_expires == DateTime.MinValue)
+ return 0;
+
+ var expires = _expires.Kind != DateTimeKind.Local
+ ? _expires.ToLocalTime ()
+ : _expires;
+
+ var span = expires - DateTime.Now;
+
+ return span > TimeSpan.Zero
+ ? (int) span.TotalSeconds
+ : 0;
+ }
+
+ set {
+ _expires = value > 0
+ ? DateTime.Now.AddSeconds ((double) value)
+ : DateTime.Now;
+ }
+ }
+
+ internal int[] Ports {
+ get {
+ return _ports ?? _emptyPorts;
+ }
+ }
+
+ internal string SameSite {
+ get {
+ return _sameSite;
+ }
+
+ set {
+ _sameSite = value;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the value of the Comment attribute of the cookie.
+ ///
+ ///
+ ///
+ /// A that represents the comment to document
+ /// intended use of the cookie.
+ ///
+ ///
+ /// if not present.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ public string Comment {
+ get {
+ return _comment;
+ }
+
+ internal set {
+ _comment = value;
+ }
+ }
+
+ ///
+ /// Gets the value of the CommentURL attribute of the cookie.
+ ///
+ ///
+ ///
+ /// A that represents the URI that provides
+ /// the comment to document intended use of the cookie.
+ ///
+ ///
+ /// if not present.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ public Uri CommentUri {
+ get {
+ return _commentUri;
+ }
+
+ internal set {
+ _commentUri = value;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the client discards the cookie
+ /// unconditionally when the client terminates.
+ ///
+ ///
+ ///
+ /// true if the client discards the cookie unconditionally
+ /// when the client terminates; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ public bool Discard {
+ get {
+ return _discard;
+ }
+
+ internal set {
+ _discard = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the value of the Domain attribute of the cookie.
+ ///
+ ///
+ ///
+ /// A that represents the domain name that
+ /// the cookie is valid for.
+ ///
+ ///
+ /// An empty string if not necessary.
+ ///
+ ///
+ public string Domain {
+ get {
+ return _domain;
+ }
+
+ set {
+ _domain = value ?? String.Empty;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the cookie has expired.
+ ///
+ ///
+ ///
+ /// true if the cookie has expired; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ public bool Expired {
+ get {
+ return _expires != DateTime.MinValue && _expires <= DateTime.Now;
+ }
+
+ set {
+ _expires = value ? DateTime.Now : DateTime.MinValue;
+ }
+ }
+
+ ///
+ /// Gets or sets the value of the Expires attribute of the cookie.
+ ///
+ ///
+ ///
+ /// A that represents the date and time that
+ /// the cookie expires on.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ public DateTime Expires {
+ get {
+ return _expires;
+ }
+
+ set {
+ _expires = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether non-HTTP APIs can access
+ /// the cookie.
+ ///
+ ///
+ ///
+ /// true if non-HTTP APIs cannot access the cookie; otherwise,
+ /// false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ public bool HttpOnly {
+ get {
+ return _httpOnly;
+ }
+
+ set {
+ _httpOnly = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the name of the cookie.
+ ///
+ ///
+ ///
+ /// A that represents the name of the cookie.
+ ///
+ ///
+ /// The name must be a token defined in
+ ///
+ /// RFC 2616.
+ ///
+ ///
+ ///
+ ///
+ /// The value specified for a set operation is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The value specified for a set operation starts with a dollar sign.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The value specified for a set operation contains an invalid character.
+ ///
+ ///
+ ///
+ /// The value specified for a set operation is .
+ ///
+ public string Name {
+ get {
+ return _name;
+ }
+
+ set {
+ if (value == null)
+ throw new ArgumentNullException ("value");
+
+ if (value.Length == 0)
+ throw new ArgumentException ("An empty string.", "value");
+
+ if (value[0] == '$') {
+ var msg = "It starts with a dollar sign.";
+
+ throw new ArgumentException (msg, "value");
+ }
+
+ if (!value.IsToken ()) {
+ var msg = "It contains an invalid character.";
+
+ throw new ArgumentException (msg, "value");
+ }
+
+ _name = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the value of the Path attribute of the cookie.
+ ///
+ ///
+ /// A that represents the subset of URI on
+ /// the origin server that the cookie applies to.
+ ///
+ public string Path {
+ get {
+ return _path;
+ }
+
+ set {
+ _path = value ?? String.Empty;
+ }
+ }
+
+ ///
+ /// Gets the value of the Port attribute of the cookie.
+ ///
+ ///
+ ///
+ /// A that represents the list of TCP ports
+ /// that the cookie applies to.
+ ///
+ ///
+ /// if not present.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ public string Port {
+ get {
+ return _port;
+ }
+
+ internal set {
+ int[] ports;
+
+ if (!tryCreatePorts (value, out ports))
+ return;
+
+ _ports = ports;
+ _port = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the security level of
+ /// the cookie is secure.
+ ///
+ ///
+ /// When this property is true, the cookie may be included in
+ /// the request only if the request is transmitted over HTTPS.
+ ///
+ ///
+ ///
+ /// true if the security level of the cookie is secure;
+ /// otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ public bool Secure {
+ get {
+ return _secure;
+ }
+
+ set {
+ _secure = value;
+ }
+ }
+
+ ///
+ /// Gets the time when the cookie was issued.
+ ///
+ ///
+ /// A that represents the time when
+ /// the cookie was issued.
+ ///
+ public DateTime TimeStamp {
+ get {
+ return _timeStamp;
+ }
+ }
+
+ ///
+ /// Gets or sets the value of the cookie.
+ ///
+ ///
+ /// A that represents the value of the cookie.
+ ///
+ ///
+ /// The value specified for a set operation is a string not enclosed in
+ /// double quotes although it contains a reserved character.
+ ///
+ public string Value {
+ get {
+ return _value;
+ }
+
+ set {
+ if (value == null)
+ value = String.Empty;
+
+ if (value.Contains (_reservedCharsForValue)) {
+ if (!value.IsEnclosedIn ('"')) {
+ var msg = "A string not enclosed in double quotes.";
+
+ throw new ArgumentException (msg, "value");
+ }
+ }
+
+ _value = value;
+ }
+ }
+
+ ///
+ /// Gets the value of the Version attribute of the cookie.
+ ///
+ ///
+ ///
+ /// An that represents the version of HTTP state
+ /// management that the cookie conforms to.
+ ///
+ ///
+ /// 0 or 1.
+ ///
+ ///
+ /// 0 if not present.
+ ///
+ ///
+ /// The default value is 0.
+ ///
+ ///
+ public int Version {
+ get {
+ return _version;
+ }
+
+ internal set {
+ if (value < 0 || value > 1)
+ return;
+
+ _version = value;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ 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);
+ }
+
+ private void init (string name, string value, string path, string domain)
+ {
+ _name = name;
+ _value = value;
+ _path = path;
+ _domain = domain;
+
+ _expires = DateTime.MinValue;
+ _timeStamp = DateTime.Now;
+ }
+
+ private string toResponseStringVersion0 ()
+ {
+ var buff = new StringBuilder (64);
+
+ buff.AppendFormat ("{0}={1}", _name, _value);
+
+ if (_expires != DateTime.MinValue) {
+ var expires = _expires
+ .ToUniversalTime ()
+ .ToString (
+ "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'",
+ CultureInfo.CreateSpecificCulture ("en-US")
+ );
+
+ buff.AppendFormat ("; Expires={0}", expires);
+ }
+
+ if (!_path.IsNullOrEmpty ())
+ buff.AppendFormat ("; Path={0}", _path);
+
+ if (!_domain.IsNullOrEmpty ())
+ buff.AppendFormat ("; Domain={0}", _domain);
+
+ if (!_sameSite.IsNullOrEmpty ())
+ buff.AppendFormat ("; SameSite={0}", _sameSite);
+
+ if (_secure)
+ buff.Append ("; Secure");
+
+ if (_httpOnly)
+ buff.Append ("; HttpOnly");
+
+ return buff.ToString ();
+ }
+
+ private string toResponseStringVersion1 ()
+ {
+ var buff = new StringBuilder (64);
+
+ buff.AppendFormat ("{0}={1}; Version={2}", _name, _value, _version);
+
+ if (_expires != DateTime.MinValue)
+ buff.AppendFormat ("; Max-Age={0}", MaxAge);
+
+ if (!_path.IsNullOrEmpty ())
+ buff.AppendFormat ("; Path={0}", _path);
+
+ if (!_domain.IsNullOrEmpty ())
+ buff.AppendFormat ("; Domain={0}", _domain);
+
+ if (_port != null) {
+ if (_port != "\"\"")
+ buff.AppendFormat ("; Port={0}", _port);
+ else
+ buff.Append ("; Port");
+ }
+
+ if (_comment != null) {
+ var comment = HttpUtility.UrlEncode (_comment);
+
+ buff.AppendFormat ("; Comment={0}", comment);
+ }
+
+ if (_commentUri != null) {
+ var url = _commentUri.OriginalString;
+
+ buff.AppendFormat (
+ "; CommentURL={0}",
+ !url.IsToken () ? url.Quote () : url
+ );
+ }
+
+ if (_discard)
+ buff.Append ("; Discard");
+
+ if (_secure)
+ buff.Append ("; Secure");
+
+ return buff.ToString ();
+ }
+
+ private static bool tryCreatePorts (string value, out int[] result)
+ {
+ result = null;
+
+ var arr = value.Trim ('"').Split (',');
+ var len = arr.Length;
+ var res = new int[len];
+
+ for (var i = 0; i < len; i++) {
+ var s = arr[i].Trim ();
+
+ if (s.Length == 0) {
+ res[i] = Int32.MinValue;
+
+ continue;
+ }
+
+ if (!Int32.TryParse (s, out res[i]))
+ return false;
+ }
+
+ result = res;
+
+ return true;
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal bool EqualsWithoutValue (Cookie cookie)
+ {
+ var caseSensitive = StringComparison.InvariantCulture;
+ var caseInsensitive = StringComparison.InvariantCultureIgnoreCase;
+
+ return _name.Equals (cookie._name, caseInsensitive)
+ && _path.Equals (cookie._path, caseSensitive)
+ && _domain.Equals (cookie._domain, caseInsensitive)
+ && _version == cookie._version;
+ }
+
+ internal bool EqualsWithoutValueAndVersion (Cookie cookie)
+ {
+ var caseSensitive = StringComparison.InvariantCulture;
+ var caseInsensitive = StringComparison.InvariantCultureIgnoreCase;
+
+ return _name.Equals (cookie._name, caseInsensitive)
+ && _path.Equals (cookie._path, caseSensitive)
+ && _domain.Equals (cookie._domain, caseInsensitive);
+ }
+
+ internal string ToRequestString (Uri uri)
+ {
+ if (_name.Length == 0)
+ return String.Empty;
+
+ if (_version == 0)
+ return String.Format ("{0}={1}", _name, _value);
+
+ var buff = new StringBuilder (64);
+
+ buff.AppendFormat ("$Version={0}; {1}={2}", _version, _name, _value);
+
+ if (!_path.IsNullOrEmpty ())
+ buff.AppendFormat ("; $Path={0}", _path);
+ else if (uri != null)
+ buff.AppendFormat ("; $Path={0}", uri.GetAbsolutePath ());
+ else
+ buff.Append ("; $Path=/");
+
+ if (!_domain.IsNullOrEmpty ()) {
+ if (uri == null || uri.Host != _domain)
+ buff.AppendFormat ("; $Domain={0}", _domain);
+ }
+
+ if (_port != null) {
+ if (_port != "\"\"")
+ buff.AppendFormat ("; $Port={0}", _port);
+ else
+ buff.Append ("; $Port");
+ }
+
+ return buff.ToString ();
+ }
+
+ internal string ToResponseString ()
+ {
+ if (_name.Length == 0)
+ return String.Empty;
+
+ if (_version == 0)
+ return toResponseStringVersion0 ();
+
+ return toResponseStringVersion1 ();
+ }
+
+ internal static bool TryCreate (
+ string name,
+ string value,
+ out Cookie result
+ )
+ {
+ result = null;
+
+ try {
+ result = new Cookie (name, value);
+ }
+ catch {
+ return false;
+ }
+
+ return true;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Determines whether the current cookie instance is equal to
+ /// the specified instance.
+ ///
+ ///
+ ///
+ /// An instance to compare with
+ /// the current cookie instance.
+ ///
+ ///
+ /// An reference to a instance.
+ ///
+ ///
+ ///
+ /// true if the current cookie instance is equal to
+ /// ; otherwise, false.
+ ///
+ public override bool Equals (object comparand)
+ {
+ var cookie = comparand as Cookie;
+
+ if (cookie == null)
+ return false;
+
+ var caseSensitive = StringComparison.InvariantCulture;
+ var caseInsensitive = StringComparison.InvariantCultureIgnoreCase;
+
+ return _name.Equals (cookie._name, caseInsensitive)
+ && _value.Equals (cookie._value, caseSensitive)
+ && _path.Equals (cookie._path, caseSensitive)
+ && _domain.Equals (cookie._domain, caseInsensitive)
+ && _version == cookie._version;
+ }
+
+ ///
+ /// Gets a hash code for the current cookie instance.
+ ///
+ ///
+ /// An that represents the hash code.
+ ///
+ public override int GetHashCode ()
+ {
+ var i = StringComparer.InvariantCultureIgnoreCase.GetHashCode (_name);
+ var j = _value.GetHashCode ();
+ var k = _path.GetHashCode ();
+ var l = StringComparer.InvariantCultureIgnoreCase.GetHashCode (_domain);
+ var m = _version;
+
+ return hash (i, j, k, l, m);
+ }
+
+ ///
+ /// Returns a string that represents the current cookie instance.
+ ///
+ ///
+ /// A that is suitable for the Cookie request header.
+ ///
+ public override string ToString ()
+ {
+ return ToRequestString (null);
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/Cookie.cs.meta b/Assets/External/websocket-sharp/Net/Cookie.cs.meta
new file mode 100644
index 00000000..3a9da13d
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/Cookie.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 5ff89aabc6475184fa85a0cb73e53bcd
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/CookieCollection.cs b/Assets/External/websocket-sharp/Net/CookieCollection.cs
new file mode 100644
index 00000000..9a8ce641
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/CookieCollection.cs
@@ -0,0 +1,882 @@
+#region License
+/*
+ * CookieCollection.cs
+ *
+ * This code is derived from CookieCollection.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2004,2009 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2012-2024 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
+
+#region Authors
+/*
+ * Authors:
+ * - Lawrence Pit
+ * - Gonzalo Paniagua Javier
+ * - Sebastien Pouliot
+ */
+#endregion
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+
+namespace WebSocketSharp.Net
+{
+ ///
+ /// Provides a collection of instances of the class.
+ ///
+ [Serializable]
+ public class CookieCollection : ICollection
+ {
+ #region Private Fields
+
+ private List _list;
+ private bool _readOnly;
+ private object _sync;
+
+ #endregion
+
+ #region Public Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CookieCollection ()
+ {
+ _list = new List ();
+ _sync = ((ICollection) _list).SyncRoot;
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal IList List {
+ get {
+ return _list;
+ }
+ }
+
+ internal IEnumerable Sorted {
+ get {
+ var list = new List (_list);
+
+ if (list.Count > 1)
+ list.Sort (compareForSorted);
+
+ return list;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the number of cookies in the collection.
+ ///
+ ///
+ /// An that represents the number of cookies in
+ /// the collection.
+ ///
+ public int Count {
+ get {
+ return _list.Count;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the collection is read-only.
+ ///
+ ///
+ ///
+ /// true if the collection is read-only; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ public bool IsReadOnly {
+ get {
+ return _readOnly;
+ }
+
+ internal set {
+ _readOnly = value;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the access to the collection is
+ /// thread safe.
+ ///
+ ///
+ ///
+ /// true if the access to the collection is thread safe;
+ /// otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ public bool IsSynchronized {
+ get {
+ return false;
+ }
+ }
+
+ ///
+ /// Gets the cookie at the specified index from the collection.
+ ///
+ ///
+ /// A at the specified index in the collection.
+ ///
+ ///
+ /// An that specifies the zero-based index of the cookie
+ /// to find.
+ ///
+ ///
+ /// is out of allowable range for the collection.
+ ///
+ public Cookie this[int index] {
+ get {
+ if (index < 0 || index >= _list.Count)
+ throw new ArgumentOutOfRangeException ("index");
+
+ return _list[index];
+ }
+ }
+
+ ///
+ /// Gets the cookie with the specified name from the collection.
+ ///
+ ///
+ ///
+ /// A with the specified name in the collection.
+ ///
+ ///
+ /// if not found.
+ ///
+ ///
+ ///
+ /// A that specifies the name of the cookie to find.
+ ///
+ ///
+ /// is .
+ ///
+ public Cookie this[string name] {
+ get {
+ if (name == null)
+ throw new ArgumentNullException ("name");
+
+ var caseInsensitive = StringComparison.InvariantCultureIgnoreCase;
+
+ foreach (var cookie in Sorted) {
+ if (cookie.Name.Equals (name, caseInsensitive))
+ return cookie;
+ }
+
+ return null;
+ }
+ }
+
+ ///
+ /// Gets an object used to synchronize access to the collection.
+ ///
+ ///
+ /// An used to synchronize access to the collection.
+ ///
+ public object SyncRoot {
+ get {
+ return _sync;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private void add (Cookie cookie)
+ {
+ var idx = search (cookie);
+
+ if (idx == -1) {
+ _list.Add (cookie);
+
+ return;
+ }
+
+ _list[idx] = cookie;
+ }
+
+ private static int compareForSort (Cookie x, Cookie y)
+ {
+ return (x.Name.Length + x.Value.Length)
+ - (y.Name.Length + y.Value.Length);
+ }
+
+ private static int compareForSorted (Cookie x, Cookie y)
+ {
+ var ret = x.Version - y.Version;
+
+ if (ret != 0)
+ return ret;
+
+ ret = x.Name.CompareTo (y.Name);
+
+ if (ret != 0)
+ return ret;
+
+ return y.Path.Length - x.Path.Length;
+ }
+
+ private static CookieCollection parseRequest (string value)
+ {
+ var ret = new CookieCollection ();
+
+ Cookie cookie = null;
+ var ver = 0;
+ var caseInsensitive = StringComparison.InvariantCultureIgnoreCase;
+
+ var pairs = value.SplitHeaderValue (',', ';').ToList ();
+
+ for (var i = 0; i < pairs.Count; i++) {
+ var pair = pairs[i].Trim ();
+
+ if (pair.Length == 0)
+ continue;
+
+ var idx = pair.IndexOf ('=');
+
+ if (idx == -1) {
+ if (cookie == null)
+ continue;
+
+ if (pair.Equals ("$port", caseInsensitive)) {
+ cookie.Port = "\"\"";
+
+ continue;
+ }
+
+ continue;
+ }
+
+ if (idx == 0) {
+ if (cookie != null) {
+ ret.add (cookie);
+
+ cookie = null;
+ }
+
+ continue;
+ }
+
+ var name = pair.Substring (0, idx).TrimEnd (' ');
+ var val = idx < pair.Length - 1
+ ? pair.Substring (idx + 1).TrimStart (' ')
+ : String.Empty;
+
+ if (name.Equals ("$version", caseInsensitive)) {
+ if (val.Length == 0)
+ continue;
+
+ var s = val.Unquote ();
+
+ int num;
+
+ if (!Int32.TryParse (s, out num))
+ continue;
+
+ ver = num;
+
+ continue;
+ }
+
+ if (name.Equals ("$path", caseInsensitive)) {
+ if (cookie == null)
+ continue;
+
+ if (val.Length == 0)
+ continue;
+
+ cookie.Path = val;
+
+ continue;
+ }
+
+ if (name.Equals ("$domain", caseInsensitive)) {
+ if (cookie == null)
+ continue;
+
+ if (val.Length == 0)
+ continue;
+
+ cookie.Domain = val;
+
+ continue;
+ }
+
+ if (name.Equals ("$port", caseInsensitive)) {
+ if (cookie == null)
+ continue;
+
+ if (val.Length == 0)
+ continue;
+
+ cookie.Port = val;
+
+ continue;
+ }
+
+ if (cookie != null)
+ ret.add (cookie);
+
+ if (!Cookie.TryCreate (name, val, out cookie))
+ continue;
+
+ if (ver != 0)
+ cookie.Version = ver;
+ }
+
+ if (cookie != null)
+ ret.add (cookie);
+
+ return ret;
+ }
+
+ private static CookieCollection parseResponse (string value)
+ {
+ var ret = new CookieCollection ();
+
+ Cookie cookie = null;
+ var caseInsensitive = StringComparison.InvariantCultureIgnoreCase;
+
+ var pairs = value.SplitHeaderValue (',', ';').ToList ();
+
+ for (var i = 0; i < pairs.Count; i++) {
+ var pair = pairs[i].Trim ();
+
+ if (pair.Length == 0)
+ continue;
+
+ var idx = pair.IndexOf ('=');
+
+ if (idx == -1) {
+ if (cookie == null)
+ continue;
+
+ if (pair.Equals ("port", caseInsensitive)) {
+ cookie.Port = "\"\"";
+
+ continue;
+ }
+
+ if (pair.Equals ("discard", caseInsensitive)) {
+ cookie.Discard = true;
+
+ continue;
+ }
+
+ if (pair.Equals ("secure", caseInsensitive)) {
+ cookie.Secure = true;
+
+ continue;
+ }
+
+ if (pair.Equals ("httponly", caseInsensitive)) {
+ cookie.HttpOnly = true;
+
+ continue;
+ }
+
+ continue;
+ }
+
+ if (idx == 0) {
+ if (cookie != null) {
+ ret.add (cookie);
+
+ cookie = null;
+ }
+
+ continue;
+ }
+
+ var name = pair.Substring (0, idx).TrimEnd (' ');
+ var val = idx < pair.Length - 1
+ ? pair.Substring (idx + 1).TrimStart (' ')
+ : String.Empty;
+
+ if (name.Equals ("version", caseInsensitive)) {
+ if (cookie == null)
+ continue;
+
+ if (val.Length == 0)
+ continue;
+
+ var s = val.Unquote ();
+
+ int num;
+
+ if (!Int32.TryParse (s, out num))
+ continue;
+
+ cookie.Version = num;
+
+ continue;
+ }
+
+ if (name.Equals ("expires", caseInsensitive)) {
+ if (val.Length == 0)
+ continue;
+
+ if (i == pairs.Count - 1)
+ break;
+
+ i++;
+
+ if (cookie == null)
+ continue;
+
+ if (cookie.Expires != DateTime.MinValue)
+ continue;
+
+ var buff = new StringBuilder (val, 32);
+
+ buff.AppendFormat (", {0}", pairs[i].Trim ());
+
+ var s = buff.ToString ();
+ var fmts = new[] { "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", "r" };
+ var provider = CultureInfo.CreateSpecificCulture ("en-US");
+ var style = DateTimeStyles.AdjustToUniversal
+ | DateTimeStyles.AssumeUniversal;
+
+ DateTime expires;
+
+ var done = DateTime.TryParseExact (
+ s,
+ fmts,
+ provider,
+ style,
+ out expires
+ );
+
+ if (!done)
+ continue;
+
+ cookie.Expires = expires.ToLocalTime ();
+
+ continue;
+ }
+
+ if (name.Equals ("max-age", caseInsensitive)) {
+ if (cookie == null)
+ continue;
+
+ if (val.Length == 0)
+ continue;
+
+ var s = val.Unquote ();
+
+ int maxAge;
+
+ if (!Int32.TryParse (s, out maxAge))
+ continue;
+
+ cookie.MaxAge = maxAge;
+
+ continue;
+ }
+
+ if (name.Equals ("path", caseInsensitive)) {
+ if (cookie == null)
+ continue;
+
+ if (val.Length == 0)
+ continue;
+
+ cookie.Path = val;
+
+ continue;
+ }
+
+ if (name.Equals ("domain", caseInsensitive)) {
+ if (cookie == null)
+ continue;
+
+ if (val.Length == 0)
+ continue;
+
+ cookie.Domain = val;
+
+ continue;
+ }
+
+ if (name.Equals ("port", caseInsensitive)) {
+ if (cookie == null)
+ continue;
+
+ if (val.Length == 0)
+ continue;
+
+ cookie.Port = val;
+
+ continue;
+ }
+
+ if (name.Equals ("comment", caseInsensitive)) {
+ if (cookie == null)
+ continue;
+
+ if (val.Length == 0)
+ continue;
+
+ cookie.Comment = urlDecode (val, Encoding.UTF8);
+
+ continue;
+ }
+
+ if (name.Equals ("commenturl", caseInsensitive)) {
+ if (cookie == null)
+ continue;
+
+ if (val.Length == 0)
+ continue;
+
+ cookie.CommentUri = val.Unquote ().ToUri ();
+
+ continue;
+ }
+
+ if (name.Equals ("samesite", caseInsensitive)) {
+ if (cookie == null)
+ continue;
+
+ if (val.Length == 0)
+ continue;
+
+ cookie.SameSite = val.Unquote ();
+
+ continue;
+ }
+
+ if (cookie != null)
+ ret.add (cookie);
+
+ Cookie.TryCreate (name, val, out cookie);
+ }
+
+ if (cookie != null)
+ ret.add (cookie);
+
+ return ret;
+ }
+
+ private int search (Cookie cookie)
+ {
+ for (var i = _list.Count - 1; i >= 0; i--) {
+ if (_list[i].EqualsWithoutValue (cookie))
+ return i;
+ }
+
+ return -1;
+ }
+
+ private static string urlDecode (string s, Encoding encoding)
+ {
+ if (s.IndexOfAny (new[] { '%', '+' }) == -1)
+ return s;
+
+ try {
+ return HttpUtility.UrlDecode (s, encoding);
+ }
+ catch {
+ return null;
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal static CookieCollection Parse (string value, bool response)
+ {
+ try {
+ return response ? parseResponse (value) : parseRequest (value);
+ }
+ catch (Exception ex) {
+ throw new CookieException ("It could not be parsed.", ex);
+ }
+ }
+
+ internal void SetOrRemove (Cookie cookie)
+ {
+ var idx = search (cookie);
+
+ if (idx == -1) {
+ if (cookie.Expired)
+ return;
+
+ _list.Add (cookie);
+
+ return;
+ }
+
+ if (cookie.Expired) {
+ _list.RemoveAt (idx);
+
+ return;
+ }
+
+ _list[idx] = cookie;
+ }
+
+ internal void SetOrRemove (CookieCollection cookies)
+ {
+ foreach (var cookie in cookies._list)
+ SetOrRemove (cookie);
+ }
+
+ internal void Sort ()
+ {
+ if (_list.Count < 2)
+ return;
+
+ _list.Sort (compareForSort);
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Adds the specified cookie to the collection.
+ ///
+ ///
+ /// A to add.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The collection is read-only.
+ ///
+ public void Add (Cookie cookie)
+ {
+ if (_readOnly) {
+ var msg = "The collection is read-only.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (cookie == null)
+ throw new ArgumentNullException ("cookie");
+
+ add (cookie);
+ }
+
+ ///
+ /// Adds the specified cookies to the collection.
+ ///
+ ///
+ /// A that contains the cookies to add.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The collection is read-only.
+ ///
+ public void Add (CookieCollection cookies)
+ {
+ if (_readOnly) {
+ var msg = "The collection is read-only.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (cookies == null)
+ throw new ArgumentNullException ("cookies");
+
+ foreach (var cookie in cookies._list)
+ add (cookie);
+ }
+
+ ///
+ /// Removes all cookies from the collection.
+ ///
+ ///
+ /// The collection is read-only.
+ ///
+ public void Clear ()
+ {
+ if (_readOnly) {
+ var msg = "The collection is read-only.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _list.Clear ();
+ }
+
+ ///
+ /// Determines whether the collection contains the specified cookie.
+ ///
+ ///
+ /// true if the cookie is found in the collection; otherwise,
+ /// false.
+ ///
+ ///
+ /// A to find.
+ ///
+ ///
+ /// is .
+ ///
+ public bool Contains (Cookie cookie)
+ {
+ if (cookie == null)
+ throw new ArgumentNullException ("cookie");
+
+ return search (cookie) > -1;
+ }
+
+ ///
+ /// Copies the elements of the collection to the specified array,
+ /// starting at the specified index.
+ ///
+ ///
+ /// An array of that specifies the destination of
+ /// the elements copied from the collection.
+ ///
+ ///
+ /// An that specifies the zero-based index in
+ /// the array at which copying starts.
+ ///
+ ///
+ /// The space from to the end of
+ /// is not enough to copy to.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// is less than zero.
+ ///
+ public void CopyTo (Cookie[] array, int index)
+ {
+ if (array == null)
+ throw new ArgumentNullException ("array");
+
+ if (index < 0) {
+ var msg = "Less than zero.";
+
+ throw new ArgumentOutOfRangeException ("index", msg);
+ }
+
+ if (array.Length - index < _list.Count) {
+ var msg = "The available space of the array is not enough to copy to.";
+
+ throw new ArgumentException (msg);
+ }
+
+ _list.CopyTo (array, index);
+ }
+
+ ///
+ /// Gets the enumerator that iterates through the collection.
+ ///
+ ///
+ /// An
+ /// instance that can be used to iterate through the collection.
+ ///
+ public IEnumerator GetEnumerator ()
+ {
+ return _list.GetEnumerator ();
+ }
+
+ ///
+ /// Removes the specified cookie from the collection.
+ ///
+ ///
+ ///
+ /// true if the cookie is successfully removed; otherwise,
+ /// false.
+ ///
+ ///
+ /// false if the cookie is not found in the collection.
+ ///
+ ///
+ ///
+ /// A to remove.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The collection is read-only.
+ ///
+ public bool Remove (Cookie cookie)
+ {
+ if (_readOnly) {
+ var msg = "The collection is read-only.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (cookie == null)
+ throw new ArgumentNullException ("cookie");
+
+ var idx = search (cookie);
+
+ if (idx == -1)
+ return false;
+
+ _list.RemoveAt (idx);
+
+ return true;
+ }
+
+ #endregion
+
+ #region Explicit Interface Implementations
+
+ ///
+ /// Gets the enumerator that iterates through the collection.
+ ///
+ ///
+ /// An instance that can be used to iterate
+ /// through the collection.
+ ///
+ IEnumerator IEnumerable.GetEnumerator ()
+ {
+ return _list.GetEnumerator ();
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/CookieCollection.cs.meta b/Assets/External/websocket-sharp/Net/CookieCollection.cs.meta
new file mode 100644
index 00000000..75bac1c7
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/CookieCollection.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 9324917a904d21243a764a5afcb4dde4
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/CookieException.cs b/Assets/External/websocket-sharp/Net/CookieException.cs
new file mode 100644
index 00000000..3c9ab3f9
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/CookieException.cs
@@ -0,0 +1,169 @@
+#region License
+/*
+ * CookieException.cs
+ *
+ * This code is derived from CookieException.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2024 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
+
+#region Authors
+/*
+ * Authors:
+ * - Lawrence Pit
+ */
+#endregion
+
+using System;
+using System.Runtime.Serialization;
+using System.Security.Permissions;
+
+namespace WebSocketSharp.Net
+{
+ ///
+ /// The exception that is thrown when a gets an error.
+ ///
+ [Serializable]
+ public class CookieException : FormatException, ISerializable
+ {
+ #region Internal Constructors
+
+ internal CookieException (string message)
+ : base (message)
+ {
+ }
+
+ internal CookieException (string message, Exception innerException)
+ : base (message, innerException)
+ {
+ }
+
+ #endregion
+
+ #region Protected Constructors
+
+ ///
+ /// Initializes a new instance of the class
+ /// with the specified serialized data.
+ ///
+ ///
+ /// A that contains the serialized
+ /// object data.
+ ///
+ ///
+ /// A that specifies the source for
+ /// the deserialization.
+ ///
+ ///
+ /// is .
+ ///
+ protected CookieException (
+ SerializationInfo serializationInfo,
+ StreamingContext streamingContext
+ )
+ : base (serializationInfo, streamingContext)
+ {
+ }
+
+ #endregion
+
+ #region Public Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CookieException ()
+ : base ()
+ {
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Populates the specified instance with
+ /// the data needed to serialize the current instance.
+ ///
+ ///
+ /// A that holds the serialized object data.
+ ///
+ ///
+ /// A that specifies the destination for
+ /// the serialization.
+ ///
+ ///
+ /// is .
+ ///
+ [
+ SecurityPermission (
+ SecurityAction.LinkDemand,
+ Flags = SecurityPermissionFlag.SerializationFormatter
+ )
+ ]
+ public override void GetObjectData (
+ SerializationInfo serializationInfo,
+ StreamingContext streamingContext
+ )
+ {
+ base.GetObjectData (serializationInfo, streamingContext);
+ }
+
+ #endregion
+
+ #region Explicit Interface Implementation
+
+ ///
+ /// Populates the specified instance with
+ /// the data needed to serialize the current instance.
+ ///
+ ///
+ /// A that holds the serialized object data.
+ ///
+ ///
+ /// A that specifies the destination for
+ /// the serialization.
+ ///
+ ///
+ /// is .
+ ///
+ [
+ SecurityPermission (
+ SecurityAction.LinkDemand,
+ Flags = SecurityPermissionFlag.SerializationFormatter,
+ SerializationFormatter = true
+ )
+ ]
+ void ISerializable.GetObjectData (
+ SerializationInfo serializationInfo,
+ StreamingContext streamingContext
+ )
+ {
+ base.GetObjectData (serializationInfo, streamingContext);
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/CookieException.cs.meta b/Assets/External/websocket-sharp/Net/CookieException.cs.meta
new file mode 100644
index 00000000..55a5956c
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/CookieException.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 00c4066ad16ffd145815887061f4c68d
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/EndPointListener.cs b/Assets/External/websocket-sharp/Net/EndPointListener.cs
new file mode 100644
index 00000000..3c8b3653
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/EndPointListener.cs
@@ -0,0 +1,604 @@
+#region License
+/*
+ * EndPointListener.cs
+ *
+ * This code is derived from EndPointListener.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2012-2023 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+#region Contributors
+/*
+ * Contributors:
+ * - Liryna
+ * - Nicholas Devenish
+ */
+#endregion
+
+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
+{
+ internal sealed class EndPointListener
+ {
+ #region Private Fields
+
+ private List _all; // host == '+'
+ private Dictionary _connections;
+ private object _connectionsSync;
+ private static readonly string _defaultCertFolderPath;
+ private IPEndPoint _endpoint;
+ private List _prefixes;
+ private bool _secure;
+ private Socket _socket;
+ private ServerSslConfiguration _sslConfig;
+ private List _unhandled; // host == '*'
+
+ #endregion
+
+ #region Static Constructor
+
+ static EndPointListener ()
+ {
+ _defaultCertFolderPath = Environment.GetFolderPath (
+ Environment.SpecialFolder.ApplicationData
+ );
+ }
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal EndPointListener (
+ IPEndPoint endpoint,
+ bool secure,
+ string certificateFolderPath,
+ ServerSslConfiguration sslConfig,
+ bool reuseAddress
+ )
+ {
+ _endpoint = endpoint;
+
+ if (secure) {
+ var cert = getCertificate (
+ endpoint.Port,
+ certificateFolderPath,
+ sslConfig.ServerCertificate
+ );
+
+ if (cert == null) {
+ var msg = "No server certificate could be found.";
+
+ throw new ArgumentException (msg);
+ }
+
+ _secure = true;
+ _sslConfig = new ServerSslConfiguration (sslConfig);
+ _sslConfig.ServerCertificate = cert;
+ }
+
+ _prefixes = new List ();
+ _connections = new Dictionary ();
+ _connectionsSync = ((ICollection) _connections).SyncRoot;
+
+ _socket = new Socket (
+ endpoint.Address.AddressFamily,
+ SocketType.Stream,
+ ProtocolType.Tcp
+ );
+
+ if (reuseAddress) {
+ _socket.SetSocketOption (
+ SocketOptionLevel.Socket,
+ SocketOptionName.ReuseAddress,
+ true
+ );
+ }
+
+ _socket.Bind (endpoint);
+ _socket.Listen (500);
+ _socket.BeginAccept (onAccept, this);
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ public IPAddress Address {
+ get {
+ return _endpoint.Address;
+ }
+ }
+
+ public bool IsSecure {
+ get {
+ return _secure;
+ }
+ }
+
+ public int Port {
+ get {
+ return _endpoint.Port;
+ }
+ }
+
+ public ServerSslConfiguration SslConfiguration {
+ get {
+ return _sslConfig;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private static void addSpecial (
+ List prefixes,
+ HttpListenerPrefix prefix
+ )
+ {
+ var path = prefix.Path;
+
+ foreach (var pref in prefixes) {
+ if (pref.Path == path) {
+ var msg = "The prefix is already in use.";
+
+ throw new HttpListenerException (87, msg);
+ }
+ }
+
+ prefixes.Add (prefix);
+ }
+
+ private void clearConnections ()
+ {
+ HttpConnection[] conns = null;
+
+ lock (_connectionsSync) {
+ var cnt = _connections.Count;
+
+ if (cnt == 0)
+ return;
+
+ conns = new HttpConnection[cnt];
+
+ _connections.Values.CopyTo (conns, 0);
+ _connections.Clear ();
+ }
+
+ foreach (var conn in conns)
+ conn.Close (true);
+ }
+
+ private static RSACryptoServiceProvider createRSAFromFile (string path)
+ {
+ var rsa = new RSACryptoServiceProvider ();
+
+ var key = File.ReadAllBytes (path);
+
+ rsa.ImportCspBlob (key);
+
+ return rsa;
+ }
+
+ private static X509Certificate2 getCertificate (
+ int port,
+ string folderPath,
+ X509Certificate2 defaultCertificate
+ )
+ {
+ if (folderPath == null || folderPath.Length == 0)
+ folderPath = _defaultCertFolderPath;
+
+ try {
+ var cer = Path.Combine (folderPath, String.Format ("{0}.cer", port));
+ var key = Path.Combine (folderPath, String.Format ("{0}.key", port));
+
+ var exists = File.Exists (cer) && File.Exists (key);
+
+ if (!exists)
+ return defaultCertificate;
+
+ var cert = new X509Certificate2 (cer);
+
+ cert.PrivateKey = createRSAFromFile (key);
+
+ return cert;
+ }
+ catch {
+ return defaultCertificate;
+ }
+ }
+
+ private void leaveIfNoPrefix ()
+ {
+ if (_prefixes.Count > 0)
+ return;
+
+ var prefs = _unhandled;
+
+ if (prefs != null && prefs.Count > 0)
+ return;
+
+ prefs = _all;
+
+ if (prefs != null && prefs.Count > 0)
+ return;
+
+ Close ();
+ }
+
+ private static void onAccept (IAsyncResult asyncResult)
+ {
+ var lsnr = (EndPointListener) asyncResult.AsyncState;
+
+ Socket sock = null;
+
+ try {
+ sock = lsnr._socket.EndAccept (asyncResult);
+ }
+ catch (ObjectDisposedException) {
+ return;
+ }
+ catch (Exception) {
+ // TODO: Logging.
+ }
+
+ try {
+ lsnr._socket.BeginAccept (onAccept, lsnr);
+ }
+ catch (Exception) {
+ // TODO: Logging.
+
+ if (sock != null)
+ sock.Close ();
+
+ return;
+ }
+
+ if (sock == null)
+ return;
+
+ processAccepted (sock, lsnr);
+ }
+
+ private static void processAccepted (
+ Socket socket,
+ EndPointListener listener
+ )
+ {
+ HttpConnection conn = null;
+
+ try {
+ conn = new HttpConnection (socket, listener);
+ }
+ catch (Exception) {
+ // TODO: Logging.
+
+ socket.Close ();
+
+ return;
+ }
+
+ lock (listener._connectionsSync)
+ listener._connections.Add (conn, conn);
+
+ conn.BeginReadRequest ();
+ }
+
+ private static bool removeSpecial (
+ List prefixes,
+ HttpListenerPrefix prefix
+ )
+ {
+ var path = prefix.Path;
+ var cnt = prefixes.Count;
+
+ for (var i = 0; i < cnt; i++) {
+ if (prefixes[i].Path == path) {
+ prefixes.RemoveAt (i);
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static HttpListener searchHttpListenerFromSpecial (
+ string path,
+ List prefixes
+ )
+ {
+ if (prefixes == null)
+ return null;
+
+ HttpListener ret = null;
+
+ var bestLen = -1;
+
+ foreach (var pref in prefixes) {
+ var prefPath = pref.Path;
+ var len = prefPath.Length;
+
+ if (len < bestLen)
+ continue;
+
+ var match = path.StartsWith (prefPath, StringComparison.Ordinal);
+
+ if (!match)
+ continue;
+
+ bestLen = len;
+ ret = pref.Listener;
+ }
+
+ return ret;
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal static bool CertificateExists (int port, string folderPath)
+ {
+ if (folderPath == null || folderPath.Length == 0)
+ folderPath = _defaultCertFolderPath;
+
+ var cer = Path.Combine (folderPath, String.Format ("{0}.cer", port));
+ var key = Path.Combine (folderPath, String.Format ("{0}.key", port));
+
+ return File.Exists (cer) && File.Exists (key);
+ }
+
+ internal void RemoveConnection (HttpConnection connection)
+ {
+ lock (_connectionsSync)
+ _connections.Remove (connection);
+ }
+
+ internal bool TrySearchHttpListener (Uri uri, out HttpListener listener)
+ {
+ listener = null;
+
+ if (uri == null)
+ return false;
+
+ var host = uri.Host;
+ var dns = Uri.CheckHostName (host) == UriHostNameType.Dns;
+ var port = uri.Port.ToString ();
+ var path = HttpUtility.UrlDecode (uri.AbsolutePath);
+
+ if (path[path.Length - 1] != '/')
+ path += "/";
+
+ if (host != null && host.Length > 0) {
+ var prefs = _prefixes;
+ var bestLen = -1;
+
+ foreach (var pref in prefs) {
+ if (dns) {
+ var prefHost = pref.Host;
+ var prefDns = Uri.CheckHostName (prefHost) == UriHostNameType.Dns;
+
+ if (prefDns) {
+ if (prefHost != host)
+ continue;
+ }
+ }
+
+ if (pref.Port != port)
+ continue;
+
+ var prefPath = pref.Path;
+ var len = prefPath.Length;
+
+ if (len < bestLen)
+ continue;
+
+ var match = path.StartsWith (prefPath, StringComparison.Ordinal);
+
+ if (!match)
+ continue;
+
+ bestLen = len;
+ listener = pref.Listener;
+ }
+
+ if (bestLen != -1)
+ return true;
+ }
+
+ listener = searchHttpListenerFromSpecial (path, _unhandled);
+
+ if (listener != null)
+ return true;
+
+ listener = searchHttpListenerFromSpecial (path, _all);
+
+ return listener != null;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public void AddPrefix (HttpListenerPrefix prefix)
+ {
+ List current, future;
+
+ if (prefix.Host == "*") {
+ do {
+ current = _unhandled;
+ future = current != null
+ ? new List (current)
+ : new List ();
+
+ 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 ();
+
+ addSpecial (future, prefix);
+ }
+ while (
+ Interlocked.CompareExchange (ref _all, future, current)
+ != current
+ );
+
+ return;
+ }
+
+ do {
+ current = _prefixes;
+
+ var idx = current.IndexOf (prefix);
+
+ if (idx > -1) {
+ if (current[idx].Listener != prefix.Listener) {
+ var fmt = "There is another listener for {0}.";
+ var msg = String.Format (fmt, prefix);
+
+ throw new HttpListenerException (87, msg);
+ }
+
+ return;
+ }
+
+ future = new List (current);
+
+ future.Add (prefix);
+ }
+ while (
+ Interlocked.CompareExchange (ref _prefixes, future, current)
+ != current
+ );
+ }
+
+ public void Close ()
+ {
+ _socket.Close ();
+
+ clearConnections ();
+ EndPointManager.RemoveEndPoint (_endpoint);
+ }
+
+ public void RemovePrefix (HttpListenerPrefix prefix)
+ {
+ List current, future;
+
+ if (prefix.Host == "*") {
+ do {
+ current = _unhandled;
+
+ if (current == null)
+ break;
+
+ future = new List (current);
+
+ if (!removeSpecial (future, prefix))
+ break;
+ }
+ while (
+ Interlocked.CompareExchange (ref _unhandled, future, current)
+ != current
+ );
+
+ leaveIfNoPrefix ();
+
+ return;
+ }
+
+ if (prefix.Host == "+") {
+ do {
+ current = _all;
+
+ if (current == null)
+ break;
+
+ future = new List (current);
+
+ if (!removeSpecial (future, prefix))
+ break;
+ }
+ while (
+ Interlocked.CompareExchange (ref _all, future, current)
+ != current
+ );
+
+ leaveIfNoPrefix ();
+
+ return;
+ }
+
+ do {
+ current = _prefixes;
+
+ if (!current.Contains (prefix))
+ break;
+
+ future = new List (current);
+
+ future.Remove (prefix);
+ }
+ while (
+ Interlocked.CompareExchange (ref _prefixes, future, current)
+ != current
+ );
+
+ leaveIfNoPrefix ();
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/EndPointListener.cs.meta b/Assets/External/websocket-sharp/Net/EndPointListener.cs.meta
new file mode 100644
index 00000000..1700336d
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/EndPointListener.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 9545c19e60d2db24db67dfb6448ffb6b
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/EndPointManager.cs b/Assets/External/websocket-sharp/Net/EndPointManager.cs
new file mode 100644
index 00000000..ac4582b2
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/EndPointManager.cs
@@ -0,0 +1,261 @@
+#region License
+/*
+ * EndPointManager.cs
+ *
+ * This code is derived from EndPointManager.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2012-2020 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+#region Contributors
+/*
+ * Contributors:
+ * - Liryna
+ */
+#endregion
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Net;
+
+namespace WebSocketSharp.Net
+{
+ internal sealed class EndPointManager
+ {
+ #region Private Fields
+
+ private static readonly Dictionary _endpoints;
+
+ #endregion
+
+ #region Static Constructor
+
+ static EndPointManager ()
+ {
+ _endpoints = new Dictionary ();
+ }
+
+ #endregion
+
+ #region Private Constructors
+
+ private EndPointManager ()
+ {
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private static void addPrefix (string uriPrefix, HttpListener listener)
+ {
+ var pref = new HttpListenerPrefix (uriPrefix, listener);
+
+ var addr = convertToIPAddress (pref.Host);
+
+ if (addr == null) {
+ var msg = "The URI prefix includes an invalid host.";
+
+ throw new HttpListenerException (87, msg);
+ }
+
+ if (!addr.IsLocal ()) {
+ var msg = "The URI prefix includes an invalid host.";
+
+ throw new HttpListenerException (87, msg);
+ }
+
+ int port;
+
+ if (!Int32.TryParse (pref.Port, out port)) {
+ var msg = "The URI prefix includes an invalid port.";
+
+ throw new HttpListenerException (87, msg);
+ }
+
+ if (!port.IsPortNumber ()) {
+ var msg = "The URI prefix includes an invalid port.";
+
+ throw new HttpListenerException (87, msg);
+ }
+
+ var path = pref.Path;
+
+ if (path.IndexOf ('%') != -1) {
+ var msg = "The URI prefix includes an invalid path.";
+
+ throw new HttpListenerException (87, msg);
+ }
+
+ if (path.IndexOf ("//", StringComparison.Ordinal) != -1) {
+ var msg = "The URI prefix includes an invalid path.";
+
+ throw new HttpListenerException (87, msg);
+ }
+
+ var endpoint = new IPEndPoint (addr, port);
+
+ EndPointListener lsnr;
+
+ if (_endpoints.TryGetValue (endpoint, out lsnr)) {
+ if (lsnr.IsSecure ^ pref.IsSecure) {
+ var msg = "The URI prefix includes an invalid scheme.";
+
+ throw new HttpListenerException (87, msg);
+ }
+ }
+ else {
+ lsnr = new EndPointListener (
+ endpoint,
+ pref.IsSecure,
+ listener.CertificateFolderPath,
+ listener.SslConfiguration,
+ listener.ReuseAddress
+ );
+
+ _endpoints.Add (endpoint, lsnr);
+ }
+
+ lsnr.AddPrefix (pref);
+ }
+
+ private static IPAddress convertToIPAddress (string hostname)
+ {
+ if (hostname == "*")
+ return IPAddress.Any;
+
+ if (hostname == "+")
+ return IPAddress.Any;
+
+ return hostname.ToIPAddress ();
+ }
+
+ private static void removePrefix (string uriPrefix, HttpListener listener)
+ {
+ var pref = new HttpListenerPrefix (uriPrefix, listener);
+
+ var addr = convertToIPAddress (pref.Host);
+
+ if (addr == null)
+ return;
+
+ if (!addr.IsLocal ())
+ return;
+
+ int port;
+
+ if (!Int32.TryParse (pref.Port, out port))
+ return;
+
+ if (!port.IsPortNumber ())
+ return;
+
+ var path = pref.Path;
+
+ if (path.IndexOf ('%') != -1)
+ return;
+
+ if (path.IndexOf ("//", StringComparison.Ordinal) != -1)
+ return;
+
+ var endpoint = new IPEndPoint (addr, port);
+
+ EndPointListener lsnr;
+
+ if (!_endpoints.TryGetValue (endpoint, out lsnr))
+ return;
+
+ if (lsnr.IsSecure ^ pref.IsSecure)
+ return;
+
+ lsnr.RemovePrefix (pref);
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal static bool RemoveEndPoint (IPEndPoint endpoint)
+ {
+ lock (((ICollection) _endpoints).SyncRoot)
+ return _endpoints.Remove (endpoint);
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public static void AddListener (HttpListener listener)
+ {
+ var added = new List ();
+
+ lock (((ICollection) _endpoints).SyncRoot) {
+ try {
+ foreach (var pref in listener.Prefixes) {
+ addPrefix (pref, listener);
+ added.Add (pref);
+ }
+ }
+ catch {
+ foreach (var pref in added)
+ removePrefix (pref, listener);
+
+ throw;
+ }
+ }
+ }
+
+ public static void AddPrefix (string uriPrefix, HttpListener listener)
+ {
+ lock (((ICollection) _endpoints).SyncRoot)
+ addPrefix (uriPrefix, listener);
+ }
+
+ public static void RemoveListener (HttpListener listener)
+ {
+ lock (((ICollection) _endpoints).SyncRoot) {
+ foreach (var pref in listener.Prefixes)
+ removePrefix (pref, listener);
+ }
+ }
+
+ public static void RemovePrefix (string uriPrefix, HttpListener listener)
+ {
+ lock (((ICollection) _endpoints).SyncRoot)
+ removePrefix (uriPrefix, listener);
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/EndPointManager.cs.meta b/Assets/External/websocket-sharp/Net/EndPointManager.cs.meta
new file mode 100644
index 00000000..76021b40
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/EndPointManager.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 420d942fd863ffb48a7a0a77a21417ea
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/HttpBasicIdentity.cs b/Assets/External/websocket-sharp/Net/HttpBasicIdentity.cs
new file mode 100644
index 00000000..d26b29f6
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpBasicIdentity.cs
@@ -0,0 +1,82 @@
+#region License
+/*
+ * HttpBasicIdentity.cs
+ *
+ * This code is derived from HttpListenerBasicIdentity.cs (System.Net) of
+ * Mono (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2014-2017 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+using System;
+using System.Security.Principal;
+
+namespace WebSocketSharp.Net
+{
+ ///
+ /// Holds the username and password from an HTTP Basic authentication attempt.
+ ///
+ public class HttpBasicIdentity : GenericIdentity
+ {
+ #region Private Fields
+
+ private string _password;
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal HttpBasicIdentity (string username, string password)
+ : base (username, "Basic")
+ {
+ _password = password;
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the password from a basic authentication attempt.
+ ///
+ ///
+ /// A that represents the password.
+ ///
+ public virtual string Password {
+ get {
+ return _password;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/HttpBasicIdentity.cs.meta b/Assets/External/websocket-sharp/Net/HttpBasicIdentity.cs.meta
new file mode 100644
index 00000000..04dae55a
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpBasicIdentity.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 8488c11ffdda6dc4cb98b844ec000b25
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/HttpConnection.cs b/Assets/External/websocket-sharp/Net/HttpConnection.cs
new file mode 100644
index 00000000..e51efb11
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpConnection.cs
@@ -0,0 +1,653 @@
+#region License
+/*
+ * HttpConnection.cs
+ *
+ * This code is derived from HttpConnection.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2012-2025 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+#region Contributors
+/*
+ * Contributors:
+ * - Liryna
+ * - Rohan Singh
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+
+namespace WebSocketSharp.Net
+{
+ internal sealed class HttpConnection
+ {
+ #region Private Fields
+
+ private int _attempts;
+ private byte[] _buffer;
+ private static readonly int _bufferLength;
+ private HttpListenerContext _context;
+ private StringBuilder _currentLine;
+ private EndPointListener _endPointListener;
+ private InputState _inputState;
+ private RequestStream _inputStream;
+ private bool _isSecure;
+ private LineState _lineState;
+ private EndPoint _localEndPoint;
+ private static readonly int _maxInputLength;
+ private ResponseStream _outputStream;
+ private int _position;
+ private EndPoint _remoteEndPoint;
+ private MemoryStream _requestBuffer;
+ private int _reuses;
+ private Socket _socket;
+ private Stream _stream;
+ private object _sync;
+ private int _timeout;
+ private Dictionary _timeoutCanceled;
+ private Timer _timer;
+
+ #endregion
+
+ #region Static Constructor
+
+ static HttpConnection ()
+ {
+ _bufferLength = 8192;
+ _maxInputLength = 32768;
+ }
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal HttpConnection (Socket socket, EndPointListener listener)
+ {
+ _socket = socket;
+ _endPointListener = listener;
+
+ var netStream = new NetworkStream (socket, false);
+
+ if (listener.IsSecure) {
+ var sslConf = listener.SslConfiguration;
+ var sslStream = new SslStream (
+ netStream,
+ false,
+ sslConf.ClientCertificateValidationCallback
+ );
+
+ sslStream.AuthenticateAsServer (
+ sslConf.ServerCertificate,
+ sslConf.ClientCertificateRequired,
+ sslConf.EnabledSslProtocols,
+ sslConf.CheckCertificateRevocation
+ );
+
+ _isSecure = true;
+ _stream = sslStream;
+ }
+ else {
+ _stream = netStream;
+ }
+
+ _buffer = new byte[_bufferLength];
+ _localEndPoint = socket.LocalEndPoint;
+ _remoteEndPoint = socket.RemoteEndPoint;
+ _sync = new object ();
+ _timeoutCanceled = new Dictionary ();
+ _timer = new Timer (onTimeout, this, Timeout.Infinite, Timeout.Infinite);
+
+ // 90k ms for first request, 15k ms from then on.
+ init (new MemoryStream (), 90000);
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ public bool IsClosed {
+ get {
+ return _socket == null;
+ }
+ }
+
+ public bool IsLocal {
+ get {
+ return ((IPEndPoint) _remoteEndPoint).Address.IsLocal ();
+ }
+ }
+
+ public bool IsSecure {
+ get {
+ return _isSecure;
+ }
+ }
+
+ public IPEndPoint LocalEndPoint {
+ get {
+ return (IPEndPoint) _localEndPoint;
+ }
+ }
+
+ public IPEndPoint RemoteEndPoint {
+ get {
+ return (IPEndPoint) _remoteEndPoint;
+ }
+ }
+
+ public int Reuses {
+ get {
+ return _reuses;
+ }
+ }
+
+ public Socket Socket {
+ get {
+ return _socket;
+ }
+ }
+
+ public Stream Stream {
+ get {
+ return _stream;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private void close ()
+ {
+ lock (_sync) {
+ if (_socket == null)
+ return;
+
+ disposeTimer ();
+ disposeRequestBuffer ();
+ disposeStream ();
+ closeSocket ();
+ }
+
+ _context.Unregister ();
+ _endPointListener.RemoveConnection (this);
+ }
+
+ private void closeSocket ()
+ {
+ try {
+ _socket.Shutdown (SocketShutdown.Both);
+ }
+ catch {
+ }
+
+ _socket.Close ();
+
+ _socket = null;
+ }
+
+ private static MemoryStream createRequestBuffer (
+ RequestStream inputStream
+ )
+ {
+ var ret = new MemoryStream ();
+
+ if (inputStream is ChunkedRequestStream) {
+ var crs = (ChunkedRequestStream) inputStream;
+
+ if (crs.HasRemainingBuffer) {
+ var buff = crs.RemainingBuffer;
+
+ ret.Write (buff, 0, buff.Length);
+ }
+
+ return ret;
+ }
+
+ var cnt = inputStream.Count;
+
+ if (cnt > 0)
+ ret.Write (inputStream.InitialBuffer, inputStream.Offset, cnt);
+
+ return ret;
+ }
+
+ private void disposeRequestBuffer ()
+ {
+ if (_requestBuffer == null)
+ return;
+
+ _requestBuffer.Dispose ();
+
+ _requestBuffer = null;
+ }
+
+ private void disposeStream ()
+ {
+ if (_stream == null)
+ return;
+
+ _stream.Dispose ();
+
+ _stream = null;
+ }
+
+ private void disposeTimer ()
+ {
+ if (_timer == null)
+ return;
+
+ try {
+ _timer.Change (Timeout.Infinite, Timeout.Infinite);
+ }
+ catch {
+ }
+
+ _timer.Dispose ();
+
+ _timer = null;
+ }
+
+ private void init (MemoryStream requestBuffer, int timeout)
+ {
+ _requestBuffer = requestBuffer;
+ _timeout = timeout;
+
+ _context = new HttpListenerContext (this);
+ _currentLine = new StringBuilder (64);
+ _inputState = InputState.RequestLine;
+ _inputStream = null;
+ _lineState = LineState.None;
+ _outputStream = null;
+ _position = 0;
+ }
+
+ private static void onRead (IAsyncResult asyncResult)
+ {
+ var conn = (HttpConnection) asyncResult.AsyncState;
+ var current = conn._attempts;
+
+ if (conn._socket == null)
+ return;
+
+ lock (conn._sync) {
+ if (conn._socket == null)
+ return;
+
+ conn._timer.Change (Timeout.Infinite, Timeout.Infinite);
+ conn._timeoutCanceled[current] = true;
+
+ var nread = 0;
+
+ try {
+ nread = conn._stream.EndRead (asyncResult);
+ }
+ catch (Exception) {
+ // TODO: Logging.
+
+ conn.close ();
+
+ return;
+ }
+
+ if (nread <= 0) {
+ conn.close ();
+
+ return;
+ }
+
+ conn._requestBuffer.Write (conn._buffer, 0, nread);
+
+ if (conn.processRequestBuffer ())
+ return;
+
+ conn.BeginReadRequest ();
+ }
+ }
+
+ private static void onTimeout (object state)
+ {
+ var conn = (HttpConnection) state;
+ var current = conn._attempts;
+
+ if (conn._socket == null)
+ return;
+
+ lock (conn._sync) {
+ if (conn._socket == null)
+ return;
+
+ if (conn._timeoutCanceled[current])
+ return;
+
+ conn._context.SendError (408);
+ }
+ }
+
+ private bool processInput (byte[] data, int length)
+ {
+ // This method returns a bool:
+ // - true Done processing
+ // - false Need more input
+
+ var req = _context.Request;
+
+ try {
+ while (true) {
+ int nread;
+ var line = readLineFrom (data, _position, length, out nread);
+
+ _position += nread;
+
+ if (line == null)
+ break;
+
+ if (line.Length == 0) {
+ if (_inputState == InputState.RequestLine)
+ continue;
+
+ if (_position > _maxInputLength)
+ _context.ErrorMessage = "Headers too long";
+
+ return true;
+ }
+
+ if (_inputState == InputState.RequestLine) {
+ req.SetRequestLine (line);
+
+ _inputState = InputState.Headers;
+ }
+ else {
+ req.AddHeader (line);
+ }
+
+ if (_context.HasErrorMessage)
+ return true;
+ }
+ }
+ catch (Exception) {
+ // TODO: Logging.
+
+ _context.ErrorMessage = "Processing failure";
+
+ return true;
+ }
+
+ if (_position >= _maxInputLength) {
+ _context.ErrorMessage = "Headers too long";
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool processRequestBuffer ()
+ {
+ // This method returns a bool:
+ // - true Done processing
+ // - false Need more write
+
+ var data = _requestBuffer.GetBuffer ();
+ var len = (int) _requestBuffer.Length;
+
+ if (!processInput (data, len))
+ return false;
+
+ var req = _context.Request;
+
+ if (!_context.HasErrorMessage)
+ req.FinishInitialization ();
+
+ if (_context.HasErrorMessage) {
+ _context.SendError ();
+
+ return true;
+ }
+
+ var uri = req.Url;
+ HttpListener httplsnr;
+
+ if (!_endPointListener.TrySearchHttpListener (uri, out httplsnr)) {
+ _context.SendError (404);
+
+ return true;
+ }
+
+ httplsnr.RegisterContext (_context);
+
+ return true;
+ }
+
+ private string readLineFrom (
+ byte[] buffer,
+ int offset,
+ int length,
+ out int nread
+ )
+ {
+ nread = 0;
+
+ for (var i = offset; i < length; i++) {
+ nread++;
+
+ var b = buffer[i];
+
+ if (b == 13) {
+ _lineState = LineState.Cr;
+
+ continue;
+ }
+
+ if (b == 10) {
+ _lineState = LineState.Lf;
+
+ break;
+ }
+
+ _currentLine.Append ((char) b);
+ }
+
+ if (_lineState != LineState.Lf)
+ return null;
+
+ var ret = _currentLine.ToString ();
+
+ _currentLine.Length = 0;
+ _lineState = LineState.None;
+
+ return ret;
+ }
+
+ private MemoryStream takeOverRequestBuffer ()
+ {
+ if (_inputStream != null)
+ return createRequestBuffer (_inputStream);
+
+ var ret = new MemoryStream ();
+
+ var buff = _requestBuffer.GetBuffer ();
+ var len = (int) _requestBuffer.Length;
+ var cnt = len - _position;
+
+ if (cnt > 0)
+ ret.Write (buff, _position, cnt);
+
+ disposeRequestBuffer ();
+
+ return ret;
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal void BeginReadRequest ()
+ {
+ _attempts++;
+
+ _timeoutCanceled.Add (_attempts, false);
+ _timer.Change (_timeout, Timeout.Infinite);
+
+ try {
+ _stream.BeginRead (_buffer, 0, _bufferLength, onRead, this);
+ }
+ catch (Exception) {
+ // TODO: Logging.
+
+ close ();
+ }
+ }
+
+ internal void Close (bool force)
+ {
+ if (_socket == null)
+ return;
+
+ lock (_sync) {
+ if (_socket == null)
+ return;
+
+ if (force) {
+ if (_outputStream != null)
+ _outputStream.Close (true);
+
+ close ();
+
+ return;
+ }
+
+ GetResponseStream ().Close (false);
+
+ if (_context.Response.CloseConnection) {
+ close ();
+
+ return;
+ }
+
+ if (!_context.Request.FlushInput ()) {
+ close ();
+
+ return;
+ }
+
+ _context.Unregister ();
+
+ _reuses++;
+
+ var buff = takeOverRequestBuffer ();
+ var len = buff.Length;
+
+ init (buff, 15000);
+
+ if (len > 0) {
+ if (processRequestBuffer ())
+ return;
+ }
+
+ BeginReadRequest ();
+ }
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public void Close ()
+ {
+ Close (false);
+ }
+
+ public RequestStream GetRequestStream (long contentLength, bool chunked)
+ {
+ lock (_sync) {
+ if (_socket == null)
+ return null;
+
+ if (_inputStream != null)
+ return _inputStream;
+
+ var buff = _requestBuffer.GetBuffer ();
+ var len = (int) _requestBuffer.Length;
+ var cnt = len - _position;
+
+ _inputStream = chunked
+ ? new ChunkedRequestStream (
+ _stream,
+ buff,
+ _position,
+ cnt,
+ _context
+ )
+ : new RequestStream (
+ _stream,
+ buff,
+ _position,
+ cnt,
+ contentLength
+ );
+
+ disposeRequestBuffer ();
+
+ return _inputStream;
+ }
+ }
+
+ public ResponseStream GetResponseStream ()
+ {
+ lock (_sync) {
+ if (_socket == null)
+ return null;
+
+ if (_outputStream != null)
+ return _outputStream;
+
+ var lsnr = _context.Listener;
+ var ignore = lsnr != null ? lsnr.IgnoreWriteExceptions : true;
+
+ _outputStream = new ResponseStream (_stream, _context.Response, ignore);
+
+ return _outputStream;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/HttpConnection.cs.meta b/Assets/External/websocket-sharp/Net/HttpConnection.cs.meta
new file mode 100644
index 00000000..8573846e
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpConnection.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 08ab2f95b185d50488a0fe48f5dd5189
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/HttpDigestIdentity.cs b/Assets/External/websocket-sharp/Net/HttpDigestIdentity.cs
new file mode 100644
index 00000000..e2863aa9
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpDigestIdentity.cs
@@ -0,0 +1,192 @@
+#region License
+/*
+ * HttpDigestIdentity.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2014-2024 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.Specialized;
+using System.Security.Principal;
+
+namespace WebSocketSharp.Net
+{
+ ///
+ /// Holds the username and other parameters from an HTTP Digest
+ /// authentication attempt.
+ ///
+ public class HttpDigestIdentity : GenericIdentity
+ {
+ #region Private Fields
+
+ private NameValueCollection _parameters;
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal HttpDigestIdentity (NameValueCollection parameters)
+ : base (parameters["username"], "Digest")
+ {
+ _parameters = parameters;
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the algorithm parameter from a digest authentication attempt.
+ ///
+ ///
+ /// A that represents the algorithm parameter.
+ ///
+ public string Algorithm {
+ get {
+ return _parameters["algorithm"];
+ }
+ }
+
+ ///
+ /// Gets the cnonce parameter from a digest authentication attempt.
+ ///
+ ///
+ /// A that represents the cnonce parameter.
+ ///
+ public string Cnonce {
+ get {
+ return _parameters["cnonce"];
+ }
+ }
+
+ ///
+ /// Gets the nc parameter from a digest authentication attempt.
+ ///
+ ///
+ /// A that represents the nc parameter.
+ ///
+ public string Nc {
+ get {
+ return _parameters["nc"];
+ }
+ }
+
+ ///
+ /// Gets the nonce parameter from a digest authentication attempt.
+ ///
+ ///
+ /// A that represents the nonce parameter.
+ ///
+ public string Nonce {
+ get {
+ return _parameters["nonce"];
+ }
+ }
+
+ ///
+ /// Gets the opaque parameter from a digest authentication attempt.
+ ///
+ ///
+ /// A that represents the opaque parameter.
+ ///
+ public string Opaque {
+ get {
+ return _parameters["opaque"];
+ }
+ }
+
+ ///
+ /// Gets the qop parameter from a digest authentication attempt.
+ ///
+ ///
+ /// A that represents the qop parameter.
+ ///
+ public string Qop {
+ get {
+ return _parameters["qop"];
+ }
+ }
+
+ ///
+ /// Gets the realm parameter from a digest authentication attempt.
+ ///
+ ///
+ /// A that represents the realm parameter.
+ ///
+ public string Realm {
+ get {
+ return _parameters["realm"];
+ }
+ }
+
+ ///
+ /// Gets the response parameter from a digest authentication attempt.
+ ///
+ ///
+ /// A that represents the response parameter.
+ ///
+ public string Response {
+ get {
+ return _parameters["response"];
+ }
+ }
+
+ ///
+ /// Gets the uri parameter from a digest authentication attempt.
+ ///
+ ///
+ /// A that represents the uri parameter.
+ ///
+ public string Uri {
+ get {
+ return _parameters["uri"];
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal bool IsValid (
+ string password,
+ string realm,
+ string method,
+ string entity
+ )
+ {
+ var parameters = new NameValueCollection (_parameters);
+
+ parameters["password"] = password;
+ parameters["realm"] = realm;
+ parameters["method"] = method;
+ parameters["entity"] = entity;
+
+ var expectedDigest = AuthenticationResponse.CreateRequestDigest (parameters);
+
+ return _parameters["response"] == expectedDigest;
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/HttpDigestIdentity.cs.meta b/Assets/External/websocket-sharp/Net/HttpDigestIdentity.cs.meta
new file mode 100644
index 00000000..58d2b565
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpDigestIdentity.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 7640330cbf592d14184f90eee16b8094
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/HttpHeaderInfo.cs b/Assets/External/websocket-sharp/Net/HttpHeaderInfo.cs
new file mode 100644
index 00000000..2b77e3d9
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpHeaderInfo.cs
@@ -0,0 +1,128 @@
+#region License
+/*
+ * HttpHeaderInfo.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2013-2020 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;
+
+namespace WebSocketSharp.Net
+{
+ internal class HttpHeaderInfo
+ {
+ #region Private Fields
+
+ private string _headerName;
+ private HttpHeaderType _headerType;
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal HttpHeaderInfo (string headerName, HttpHeaderType headerType)
+ {
+ _headerName = headerName;
+ _headerType = headerType;
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal bool IsMultiValueInRequest {
+ get {
+ var headerType = _headerType & HttpHeaderType.MultiValueInRequest;
+
+ return headerType == HttpHeaderType.MultiValueInRequest;
+ }
+ }
+
+ internal bool IsMultiValueInResponse {
+ get {
+ var headerType = _headerType & HttpHeaderType.MultiValueInResponse;
+
+ return headerType == HttpHeaderType.MultiValueInResponse;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ public string HeaderName {
+ get {
+ return _headerName;
+ }
+ }
+
+ public HttpHeaderType HeaderType {
+ get {
+ return _headerType;
+ }
+ }
+
+ public bool IsRequest {
+ get {
+ var headerType = _headerType & HttpHeaderType.Request;
+
+ return headerType == HttpHeaderType.Request;
+ }
+ }
+
+ public bool IsResponse {
+ get {
+ var headerType = _headerType & HttpHeaderType.Response;
+
+ return headerType == HttpHeaderType.Response;
+ }
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public bool IsMultiValue (bool response)
+ {
+ var headerType = _headerType & HttpHeaderType.MultiValue;
+
+ if (headerType != HttpHeaderType.MultiValue)
+ return response ? IsMultiValueInResponse : IsMultiValueInRequest;
+
+ return response ? IsResponse : IsRequest;
+ }
+
+ public bool IsRestricted (bool response)
+ {
+ var headerType = _headerType & HttpHeaderType.Restricted;
+
+ if (headerType != HttpHeaderType.Restricted)
+ return false;
+
+ return response ? IsResponse : IsRequest;
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/HttpHeaderInfo.cs.meta b/Assets/External/websocket-sharp/Net/HttpHeaderInfo.cs.meta
new file mode 100644
index 00000000..71762326
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpHeaderInfo.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 658e30bfa268d7e40a400018e371487b
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/HttpHeaderType.cs b/Assets/External/websocket-sharp/Net/HttpHeaderType.cs
new file mode 100644
index 00000000..113fb63b
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpHeaderType.cs
@@ -0,0 +1,44 @@
+#region License
+/*
+ * HttpHeaderType.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2013-2014 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;
+
+namespace WebSocketSharp.Net
+{
+ [Flags]
+ internal enum HttpHeaderType
+ {
+ Unspecified = 0,
+ Request = 1,
+ Response = 1 << 1,
+ Restricted = 1 << 2,
+ MultiValue = 1 << 3,
+ MultiValueInRequest = 1 << 4,
+ MultiValueInResponse = 1 << 5
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/HttpHeaderType.cs.meta b/Assets/External/websocket-sharp/Net/HttpHeaderType.cs.meta
new file mode 100644
index 00000000..f0437edd
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpHeaderType.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: f1d86fad644dba148ae08c1ab9e638ee
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/HttpListener.cs b/Assets/External/websocket-sharp/Net/HttpListener.cs
new file mode 100644
index 00000000..44b964c0
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpListener.cs
@@ -0,0 +1,1028 @@
+#region License
+/*
+ * HttpListener.cs
+ *
+ * This code is derived from HttpListener.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2012-2024 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+#region Contributors
+/*
+ * Contributors:
+ * - Liryna
+ */
+#endregion
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Security.Cryptography.X509Certificates;
+using System.Security.Principal;
+using System.Threading;
+
+// TODO: Logging.
+namespace WebSocketSharp.Net
+{
+ ///
+ /// Provides a simple, programmatically controlled HTTP listener.
+ ///
+ ///
+ ///
+ /// The listener supports HTTP/1.1 version request and response.
+ ///
+ ///
+ /// And the listener allows to accept WebSocket handshake requests.
+ ///
+ ///
+ /// This class cannot be inherited.
+ ///
+ ///
+ public sealed class HttpListener : IDisposable
+ {
+ #region Private Fields
+
+ private AuthenticationSchemes _authSchemes;
+ private Func _authSchemeSelector;
+ private string _certFolderPath;
+ private Queue _contextQueue;
+ private LinkedList _contextRegistry;
+ private object _contextRegistrySync;
+ private static readonly string _defaultRealm;
+ private bool _disposed;
+ private bool _ignoreWriteExceptions;
+ private volatile bool _isListening;
+ private Logger _log;
+ private HttpListenerPrefixCollection _prefixes;
+ private string _realm;
+ private bool _reuseAddress;
+ private ServerSslConfiguration _sslConfig;
+ private object _sync;
+ private Func _userCredFinder;
+ private Queue _waitQueue;
+
+ #endregion
+
+ #region Static Constructor
+
+ static HttpListener ()
+ {
+ _defaultRealm = "SECRET AREA";
+ }
+
+ #endregion
+
+ #region Public Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public HttpListener ()
+ {
+ _authSchemes = AuthenticationSchemes.Anonymous;
+ _contextQueue = new Queue ();
+ _contextRegistry = new LinkedList ();
+ _contextRegistrySync = ((ICollection) _contextRegistry).SyncRoot;
+ _log = new Logger ();
+ _prefixes = new HttpListenerPrefixCollection (this);
+ _sync = new object ();
+ _waitQueue = new Queue ();
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal string ObjectName {
+ get {
+ return GetType ().ToString ();
+ }
+ }
+
+ internal bool ReuseAddress {
+ get {
+ return _reuseAddress;
+ }
+
+ set {
+ _reuseAddress = value;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets or sets the scheme used to authenticate the clients.
+ ///
+ ///
+ ///
+ /// One of the
+ /// enum values.
+ ///
+ ///
+ /// It represents the scheme used to authenticate the clients.
+ ///
+ ///
+ /// The default value is
+ /// .
+ ///
+ ///
+ ///
+ /// This listener has been closed.
+ ///
+ public AuthenticationSchemes AuthenticationSchemes {
+ get {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ return _authSchemes;
+ }
+
+ set {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ _authSchemes = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the delegate called to determine the scheme used to
+ /// authenticate the clients.
+ ///
+ ///
+ ///
+ /// If this property is set, the listener uses the authentication
+ /// scheme selected by the delegate for each request.
+ ///
+ ///
+ /// Or if this property is not set, the listener uses the value of
+ /// the property
+ /// as the authentication scheme for all requests.
+ ///
+ ///
+ ///
+ ///
+ /// A
+ /// delegate.
+ ///
+ ///
+ /// It represents the delegate called when the listener selects
+ /// an authentication scheme.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ ///
+ /// This listener has been closed.
+ ///
+ public Func AuthenticationSchemeSelector {
+ get {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ return _authSchemeSelector;
+ }
+
+ set {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ _authSchemeSelector = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the path to the folder in which stores the certificate
+ /// files used to authenticate the server on the secure connection.
+ ///
+ ///
+ ///
+ /// This property represents the path to the folder in which stores
+ /// the certificate files associated with each port number of added
+ /// URI prefixes.
+ ///
+ ///
+ /// A set of the certificate files is a pair of <port number>.cer
+ /// (DER) and <port number>.key (DER, RSA Private Key).
+ ///
+ ///
+ /// If this property is or an empty string,
+ /// the result of the
+ /// with the method is used as
+ /// the default path.
+ ///
+ ///
+ ///
+ ///
+ /// A that represents the path to the folder
+ /// in which stores the certificate files.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ ///
+ /// This listener has been closed.
+ ///
+ public string CertificateFolderPath {
+ get {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ return _certFolderPath;
+ }
+
+ set {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ _certFolderPath = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the listener returns
+ /// exceptions that occur when sending the response to the client.
+ ///
+ ///
+ ///
+ /// true if the listener should not return those exceptions;
+ /// otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ ///
+ /// This listener has been closed.
+ ///
+ public bool IgnoreWriteExceptions {
+ get {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ return _ignoreWriteExceptions;
+ }
+
+ set {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ _ignoreWriteExceptions = value;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the listener has been started.
+ ///
+ ///
+ /// true if the listener has been started; otherwise, false.
+ ///
+ public bool IsListening {
+ get {
+ return _isListening;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the listener can be used with
+ /// the current operating system.
+ ///
+ ///
+ /// true.
+ ///
+ public static bool IsSupported {
+ get {
+ return true;
+ }
+ }
+
+ ///
+ /// Gets the logging functions.
+ ///
+ ///
+ ///
+ /// The default logging level is .
+ ///
+ ///
+ /// If you would like to change it, you should set the Log.Level
+ /// property to any of the enum values.
+ ///
+ ///
+ ///
+ /// A that provides the logging functions.
+ ///
+ ///
+ /// This listener has been closed.
+ ///
+ public Logger Log {
+ get {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ return _log;
+ }
+ }
+
+ ///
+ /// Gets the URI prefixes handled by the listener.
+ ///
+ ///
+ /// A that contains the URI
+ /// prefixes.
+ ///
+ ///
+ /// This listener has been closed.
+ ///
+ public HttpListenerPrefixCollection Prefixes {
+ get {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ return _prefixes;
+ }
+ }
+
+ ///
+ /// Gets or sets the name of the realm associated with the listener.
+ ///
+ ///
+ /// If this property is or an empty string,
+ /// "SECRET AREA" is used as the name of the realm.
+ ///
+ ///
+ ///
+ /// A that represents the name of the realm.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ ///
+ /// This listener has been closed.
+ ///
+ public string Realm {
+ get {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ return _realm;
+ }
+
+ set {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ _realm = value;
+ }
+ }
+
+ ///
+ /// Gets the configuration for secure connection.
+ ///
+ ///
+ /// A that represents the
+ /// configuration used to provide secure connections.
+ ///
+ ///
+ /// This listener has been closed.
+ ///
+ public ServerSslConfiguration SslConfiguration {
+ get {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (_sslConfig == null)
+ _sslConfig = new ServerSslConfiguration ();
+
+ return _sslConfig;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether, when NTLM authentication is used,
+ /// the authentication information of first request is used to authenticate
+ /// additional requests on the same connection.
+ ///
+ ///
+ /// This property is not currently supported and always throws
+ /// a .
+ ///
+ ///
+ /// true if the authentication information of first request is used;
+ /// otherwise, false.
+ ///
+ ///
+ /// Any use of this property.
+ ///
+ public bool UnsafeConnectionNtlmAuthentication {
+ get {
+ throw new NotSupportedException ();
+ }
+
+ set {
+ throw new NotSupportedException ();
+ }
+ }
+
+ ///
+ /// Gets or sets the delegate called to find the credentials for
+ /// an identity used to authenticate a client.
+ ///
+ ///
+ ///
+ /// A
+ /// delegate.
+ ///
+ ///
+ /// It represents the delegate called when the listener finds
+ /// the credentials used to authenticate a client.
+ ///
+ ///
+ /// It must return if the credentials
+ /// are not found.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ ///
+ /// This listener has been closed.
+ ///
+ public Func UserCredentialsFinder {
+ get {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ return _userCredFinder;
+ }
+
+ set {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ _userCredFinder = value;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private bool authenticateClient (HttpListenerContext context)
+ {
+ var schm = selectAuthenticationScheme (context.Request);
+
+ if (schm == AuthenticationSchemes.Anonymous)
+ return true;
+
+ if (schm == AuthenticationSchemes.None) {
+ var msg = "Authentication not allowed";
+
+ context.SendError (403, msg);
+
+ return false;
+ }
+
+ var realm = getRealm ();
+
+ if (!context.SetUser (schm, realm, _userCredFinder)) {
+ context.SendAuthenticationChallenge (schm, realm);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ private HttpListenerAsyncResult beginGetContext (
+ AsyncCallback callback,
+ object state
+ )
+ {
+ lock (_contextRegistrySync) {
+ if (!_isListening) {
+ var msg = "The method is canceled.";
+
+ throw new HttpListenerException (995, msg);
+ }
+
+ var ares = new HttpListenerAsyncResult (callback, state, _log);
+
+ if (_contextQueue.Count == 0) {
+ _waitQueue.Enqueue (ares);
+
+ return ares;
+ }
+
+ var ctx = _contextQueue.Dequeue ();
+
+ ares.Complete (ctx, true);
+
+ return ares;
+ }
+ }
+
+ private void cleanupContextQueue (bool force)
+ {
+ if (_contextQueue.Count == 0)
+ return;
+
+ if (force) {
+ _contextQueue.Clear ();
+
+ return;
+ }
+
+ var ctxs = _contextQueue.ToArray ();
+
+ _contextQueue.Clear ();
+
+ foreach (var ctx in ctxs)
+ ctx.SendError (503);
+ }
+
+ private void cleanupContextRegistry ()
+ {
+ var cnt = _contextRegistry.Count;
+
+ if (cnt == 0)
+ return;
+
+ var ctxs = new HttpListenerContext[cnt];
+
+ lock (_contextRegistrySync) {
+ _contextRegistry.CopyTo (ctxs, 0);
+ _contextRegistry.Clear ();
+ }
+
+ foreach (var ctx in ctxs)
+ ctx.Connection.Close (true);
+ }
+
+ private void cleanupWaitQueue (string message)
+ {
+ if (_waitQueue.Count == 0)
+ return;
+
+ var aress = _waitQueue.ToArray ();
+
+ _waitQueue.Clear ();
+
+ foreach (var ares in aress) {
+ var ex = new HttpListenerException (995, message);
+
+ ares.Complete (ex);
+ }
+ }
+
+ private void close (bool force)
+ {
+ lock (_sync) {
+ if (_disposed)
+ return;
+
+ lock (_contextRegistrySync) {
+ if (!_isListening) {
+ _disposed = true;
+
+ return;
+ }
+
+ _isListening = false;
+ }
+
+ cleanupContextQueue (force);
+ cleanupContextRegistry ();
+
+ var msg = "The listener is closed.";
+
+ cleanupWaitQueue (msg);
+
+ EndPointManager.RemoveListener (this);
+
+ _disposed = true;
+ }
+ }
+
+ private string getRealm ()
+ {
+ var realm = _realm;
+
+ return realm != null && realm.Length > 0 ? realm : _defaultRealm;
+ }
+
+ private bool registerContext (HttpListenerContext context)
+ {
+ if (!_isListening)
+ return false;
+
+ lock (_contextRegistrySync) {
+ if (!_isListening)
+ return false;
+
+ context.Listener = this;
+
+ _contextRegistry.AddLast (context);
+
+ if (_waitQueue.Count == 0) {
+ _contextQueue.Enqueue (context);
+
+ return true;
+ }
+
+ var ares = _waitQueue.Dequeue ();
+
+ ares.Complete (context, false);
+
+ return true;
+ }
+ }
+
+ private AuthenticationSchemes selectAuthenticationScheme (
+ HttpListenerRequest request
+ )
+ {
+ var selector = _authSchemeSelector;
+
+ if (selector == null)
+ return _authSchemes;
+
+ try {
+ return selector (request);
+ }
+ catch {
+ return AuthenticationSchemes.None;
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal void CheckDisposed ()
+ {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+ }
+
+ internal bool RegisterContext (HttpListenerContext context)
+ {
+ if (!authenticateClient (context))
+ return false;
+
+ if (!registerContext (context)) {
+ context.SendError (503);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ internal void UnregisterContext (HttpListenerContext context)
+ {
+ lock (_contextRegistrySync)
+ _contextRegistry.Remove (context);
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Shuts down the listener immediately.
+ ///
+ public void Abort ()
+ {
+ if (_disposed)
+ return;
+
+ close (true);
+ }
+
+ ///
+ /// Begins getting an incoming request asynchronously.
+ ///
+ ///
+ ///
+ /// This asynchronous operation must be ended by calling
+ /// the method.
+ ///
+ ///
+ /// Typically, the method is called by
+ /// .
+ ///
+ ///
+ ///
+ /// An instance that represents the status of
+ /// the asynchronous operation.
+ ///
+ ///
+ ///
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the asynchronous operation is
+ /// complete.
+ ///
+ ///
+ ///
+ /// An that specifies a user defined object to pass to
+ /// .
+ ///
+ ///
+ /// This method is canceled.
+ ///
+ ///
+ ///
+ /// This listener has not been started or is currently stopped.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// This listener has no URI prefix on which listens.
+ ///
+ ///
+ ///
+ /// This listener has been closed.
+ ///
+ public IAsyncResult BeginGetContext (AsyncCallback callback, object state)
+ {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (!_isListening) {
+ var msg = "The listener has not been started.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (_prefixes.Count == 0) {
+ var msg = "The listener has no URI prefix on which listens.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return beginGetContext (callback, state);
+ }
+
+ ///
+ /// Shuts down the listener.
+ ///
+ public void Close ()
+ {
+ if (_disposed)
+ return;
+
+ close (false);
+ }
+
+ ///
+ /// Ends an asynchronous operation to get an incoming request.
+ ///
+ ///
+ /// This method ends an asynchronous operation started by calling
+ /// the method.
+ ///
+ ///
+ /// A that represents a request.
+ ///
+ ///
+ /// An instance obtained by calling
+ /// the method.
+ ///
+ ///
+ /// was not obtained by calling
+ /// the method.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// This method is canceled.
+ ///
+ ///
+ ///
+ /// This listener has not been started or is currently stopped.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// This method was already called for .
+ ///
+ ///
+ ///
+ /// This listener has been closed.
+ ///
+ public HttpListenerContext EndGetContext (IAsyncResult asyncResult)
+ {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (!_isListening) {
+ var msg = "The listener has not been started.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (asyncResult == null)
+ throw new ArgumentNullException ("asyncResult");
+
+ var ares = asyncResult as HttpListenerAsyncResult;
+
+ if (ares == null) {
+ var msg = "A wrong IAsyncResult instance.";
+
+ throw new ArgumentException (msg, "asyncResult");
+ }
+
+ lock (ares.SyncRoot) {
+ if (ares.EndCalled) {
+ var msg = "This IAsyncResult instance cannot be reused.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ ares.EndCalled = true;
+ }
+
+ if (!ares.IsCompleted)
+ ares.AsyncWaitHandle.WaitOne ();
+
+ return ares.Context;
+ }
+
+ ///
+ /// Gets an incoming request.
+ ///
+ ///
+ /// This method waits for an incoming request and returns when
+ /// a request is received.
+ ///
+ ///
+ /// A that represents a request.
+ ///
+ ///
+ /// This method is canceled.
+ ///
+ ///
+ ///
+ /// This listener has not been started or is currently stopped.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// This listener has no URI prefix on which listens.
+ ///
+ ///
+ ///
+ /// This listener has been closed.
+ ///
+ public HttpListenerContext GetContext ()
+ {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (!_isListening) {
+ var msg = "The listener has not been started.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (_prefixes.Count == 0) {
+ var msg = "The listener has no URI prefix on which listens.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ var ares = beginGetContext (null, null);
+
+ ares.EndCalled = true;
+
+ if (!ares.IsCompleted)
+ ares.AsyncWaitHandle.WaitOne ();
+
+ return ares.Context;
+ }
+
+ ///
+ /// Starts receiving incoming requests.
+ ///
+ ///
+ /// This listener has been closed.
+ ///
+ public void Start ()
+ {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ lock (_sync) {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ lock (_contextRegistrySync) {
+ if (_isListening)
+ return;
+
+ EndPointManager.AddListener (this);
+
+ _isListening = true;
+ }
+ }
+ }
+
+ ///
+ /// Stops receiving incoming requests.
+ ///
+ ///
+ /// This listener has been closed.
+ ///
+ public void Stop ()
+ {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ lock (_sync) {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ lock (_contextRegistrySync) {
+ if (!_isListening)
+ return;
+
+ _isListening = false;
+ }
+
+ cleanupContextQueue (false);
+ cleanupContextRegistry ();
+
+ var msg = "The listener is stopped.";
+
+ cleanupWaitQueue (msg);
+
+ EndPointManager.RemoveListener (this);
+ }
+ }
+
+ #endregion
+
+ #region Explicit Interface Implementations
+
+ ///
+ /// Releases all resources used by the listener.
+ ///
+ void IDisposable.Dispose ()
+ {
+ if (_disposed)
+ return;
+
+ close (true);
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/HttpListener.cs.meta b/Assets/External/websocket-sharp/Net/HttpListener.cs.meta
new file mode 100644
index 00000000..48efbd0d
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpListener.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: bbec4555545c5a740b476dbc0ef4f52a
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/HttpListenerAsyncResult.cs b/Assets/External/websocket-sharp/Net/HttpListenerAsyncResult.cs
new file mode 100644
index 00000000..e4742d77
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpListenerAsyncResult.cs
@@ -0,0 +1,202 @@
+#region License
+/*
+ * HttpListenerAsyncResult.cs
+ *
+ * This code is derived from ListenerAsyncResult.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Ximian, Inc. (http://www.ximian.com)
+ * Copyright (c) 2012-2024 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+#region Contributors
+/*
+ * Contributors:
+ * - Nicholas Devenish
+ */
+#endregion
+
+using System;
+using System.Threading;
+
+namespace WebSocketSharp.Net
+{
+ internal class HttpListenerAsyncResult : IAsyncResult
+ {
+ #region Private Fields
+
+ private AsyncCallback _callback;
+ private bool _completed;
+ private bool _completedSynchronously;
+ private HttpListenerContext _context;
+ private bool _endCalled;
+ private Exception _exception;
+ private Logger _log;
+ private object _state;
+ private object _sync;
+ private ManualResetEvent _waitHandle;
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal HttpListenerAsyncResult (
+ AsyncCallback callback,
+ object state,
+ Logger log
+ )
+ {
+ _callback = callback;
+ _state = state;
+ _log = log;
+
+ _sync = new object ();
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal HttpListenerContext Context
+ {
+ get {
+ if (_exception != null)
+ throw _exception;
+
+ return _context;
+ }
+ }
+
+ internal bool EndCalled {
+ get {
+ return _endCalled;
+ }
+
+ set {
+ _endCalled = value;
+ }
+ }
+
+ internal object SyncRoot {
+ get {
+ return _sync;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ public object AsyncState {
+ get {
+ return _state;
+ }
+ }
+
+ public WaitHandle AsyncWaitHandle {
+ get {
+ lock (_sync) {
+ if (_waitHandle == null)
+ _waitHandle = new ManualResetEvent (_completed);
+
+ return _waitHandle;
+ }
+ }
+ }
+
+ public bool CompletedSynchronously {
+ get {
+ return _completedSynchronously;
+ }
+ }
+
+ public bool IsCompleted {
+ get {
+ lock (_sync)
+ return _completed;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private void complete ()
+ {
+ lock (_sync) {
+ _completed = true;
+
+ if (_waitHandle != null)
+ _waitHandle.Set ();
+ }
+
+ if (_callback == null)
+ return;
+
+ ThreadPool.QueueUserWorkItem (
+ state => {
+ try {
+ _callback (this);
+ }
+ catch (Exception ex) {
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
+ }
+ },
+ null
+ );
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal void Complete (Exception exception)
+ {
+ _exception = exception;
+
+ complete ();
+ }
+
+ internal void Complete (
+ HttpListenerContext context,
+ bool completedSynchronously
+ )
+ {
+ _context = context;
+ _completedSynchronously = completedSynchronously;
+
+ complete ();
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/HttpListenerAsyncResult.cs.meta b/Assets/External/websocket-sharp/Net/HttpListenerAsyncResult.cs.meta
new file mode 100644
index 00000000..9d71db8a
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpListenerAsyncResult.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: d785cd8b82ce78b4fab65e7330d18fe3
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/HttpListenerContext.cs b/Assets/External/websocket-sharp/Net/HttpListenerContext.cs
new file mode 100644
index 00000000..82ea0176
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpListenerContext.cs
@@ -0,0 +1,452 @@
+#region License
+/*
+ * HttpListenerContext.cs
+ *
+ * This code is derived from HttpListenerContext.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2012-2024 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+using System;
+using System.Security.Principal;
+using System.Text;
+using WebSocketSharp.Net.WebSockets;
+
+namespace WebSocketSharp.Net
+{
+ ///
+ /// Provides the access to the HTTP request and response objects used by
+ /// the class.
+ ///
+ ///
+ /// This class cannot be inherited.
+ ///
+ public sealed class HttpListenerContext
+ {
+ #region Private Fields
+
+ private HttpConnection _connection;
+ private string _errorMessage;
+ private int _errorStatusCode;
+ private HttpListener _listener;
+ private HttpListenerRequest _request;
+ private HttpListenerResponse _response;
+ private IPrincipal _user;
+ private HttpListenerWebSocketContext _websocketContext;
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal HttpListenerContext (HttpConnection connection)
+ {
+ _connection = connection;
+
+ _errorStatusCode = 400;
+ _request = new HttpListenerRequest (this);
+ _response = new HttpListenerResponse (this);
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal HttpConnection Connection {
+ get {
+ return _connection;
+ }
+ }
+
+ internal string ErrorMessage {
+ get {
+ return _errorMessage;
+ }
+
+ set {
+ _errorMessage = value;
+ }
+ }
+
+ internal int ErrorStatusCode {
+ get {
+ return _errorStatusCode;
+ }
+
+ set {
+ _errorStatusCode = value;
+ }
+ }
+
+ internal bool HasErrorMessage {
+ get {
+ return _errorMessage != null;
+ }
+ }
+
+ internal HttpListener Listener {
+ get {
+ return _listener;
+ }
+
+ set {
+ _listener = value;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the HTTP request object that represents a client request.
+ ///
+ ///
+ /// A that represents the client request.
+ ///
+ public HttpListenerRequest Request {
+ get {
+ return _request;
+ }
+ }
+
+ ///
+ /// Gets the HTTP response object used to send a response to the client.
+ ///
+ ///
+ /// A that represents a response to
+ /// the client request.
+ ///
+ public HttpListenerResponse Response {
+ get {
+ return _response;
+ }
+ }
+
+ ///
+ /// Gets the client information.
+ ///
+ ///
+ ///
+ /// A instance that represents identity,
+ /// authentication, and security roles for the client.
+ ///
+ ///
+ /// if the client is not authenticated.
+ ///
+ ///
+ public IPrincipal User {
+ get {
+ return _user;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private static string createErrorContent (
+ int statusCode,
+ string statusDescription,
+ string message
+ )
+ {
+ return message != null && message.Length > 0
+ ? String.Format (
+ "{0} {1} ({2})
",
+ statusCode,
+ statusDescription,
+ message
+ )
+ : String.Format (
+ "{0} {1}
",
+ statusCode,
+ statusDescription
+ );
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal HttpListenerWebSocketContext GetWebSocketContext (string protocol)
+ {
+ _websocketContext = new HttpListenerWebSocketContext (this, protocol);
+
+ return _websocketContext;
+ }
+
+ internal void SendAuthenticationChallenge (
+ AuthenticationSchemes scheme,
+ string realm
+ )
+ {
+ _response.StatusCode = 401;
+
+ var val = new AuthenticationChallenge (scheme, realm).ToString ();
+
+ _response.Headers.InternalSet ("WWW-Authenticate", val, true);
+
+ _response.Close ();
+ }
+
+ internal void SendError ()
+ {
+ try {
+ _response.StatusCode = _errorStatusCode;
+ _response.ContentType = "text/html";
+
+ var content = createErrorContent (
+ _errorStatusCode,
+ _response.StatusDescription,
+ _errorMessage
+ );
+
+ var enc = Encoding.UTF8;
+ var entity = enc.GetBytes (content);
+
+ _response.ContentEncoding = enc;
+ _response.ContentLength64 = entity.LongLength;
+
+ _response.Close (entity, true);
+ }
+ catch {
+ _connection.Close (true);
+ }
+ }
+
+ internal void SendError (int statusCode)
+ {
+ _errorStatusCode = statusCode;
+
+ SendError ();
+ }
+
+ internal void SendError (int statusCode, string message)
+ {
+ _errorStatusCode = statusCode;
+ _errorMessage = message;
+
+ SendError ();
+ }
+
+ internal bool SetUser (
+ AuthenticationSchemes scheme,
+ string realm,
+ Func credentialsFinder
+ )
+ {
+ var user = HttpUtility.CreateUser (
+ _request.Headers["Authorization"],
+ scheme,
+ realm,
+ _request.HttpMethod,
+ credentialsFinder
+ );
+
+ if (user == null)
+ return false;
+
+ if (!user.Identity.IsAuthenticated)
+ return false;
+
+ _user = user;
+
+ return true;
+ }
+
+ internal void Unregister ()
+ {
+ if (_listener == null)
+ return;
+
+ _listener.UnregisterContext (this);
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Accepts a WebSocket connection.
+ ///
+ ///
+ /// A that represents
+ /// the WebSocket handshake request.
+ ///
+ ///
+ ///
+ /// A that specifies the name of the subprotocol
+ /// supported on the WebSocket connection.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ ///
+ ///
+ /// This method has already been done.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The client request is not a WebSocket handshake request.
+ ///
+ ///
+ public HttpListenerWebSocketContext AcceptWebSocket (string protocol)
+ {
+ return AcceptWebSocket (protocol, null);
+ }
+
+ ///
+ /// Accepts a WebSocket connection with initializing the WebSocket
+ /// interface.
+ ///
+ ///
+ /// A that represents
+ /// the WebSocket handshake request.
+ ///
+ ///
+ ///
+ /// A that specifies the name of the subprotocol
+ /// supported on the WebSocket connection.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ ///
+ ///
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when a new WebSocket instance is
+ /// initialized.
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// caused an exception.
+ ///
+ ///
+ ///
+ ///
+ /// This method has already been done.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The client request is not a WebSocket handshake request.
+ ///
+ ///
+ public HttpListenerWebSocketContext AcceptWebSocket (
+ string protocol,
+ Action initializer
+ )
+ {
+ if (_websocketContext != null) {
+ var msg = "The method has already been done.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (!_request.IsWebSocketRequest) {
+ var msg = "The request is not a WebSocket handshake request.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (protocol != null) {
+ if (protocol.Length == 0) {
+ var msg = "An empty string.";
+
+ throw new ArgumentException (msg, "protocol");
+ }
+
+ if (!protocol.IsToken ()) {
+ var msg = "It contains an invalid character.";
+
+ throw new ArgumentException (msg, "protocol");
+ }
+ }
+
+ var ret = GetWebSocketContext (protocol);
+
+ var ws = ret.WebSocket;
+
+ if (initializer != null) {
+ try {
+ initializer (ws);
+ }
+ catch (Exception ex) {
+ if (ws.ReadyState == WebSocketState.New)
+ _websocketContext = null;
+
+ var msg = "It caused an exception.";
+
+ throw new ArgumentException (msg, "initializer", ex);
+ }
+ }
+
+ ws.Accept ();
+
+ return ret;
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/HttpListenerContext.cs.meta b/Assets/External/websocket-sharp/Net/HttpListenerContext.cs.meta
new file mode 100644
index 00000000..8451eee8
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpListenerContext.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: a273b442ff77f394d94e4c5c941c48d7
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/HttpListenerException.cs b/Assets/External/websocket-sharp/Net/HttpListenerException.cs
new file mode 100644
index 00000000..dec858d5
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpListenerException.cs
@@ -0,0 +1,140 @@
+#region License
+/*
+ * HttpListenerException.cs
+ *
+ * This code is derived from HttpListenerException.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2012-2024 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+using System;
+using System.ComponentModel;
+using System.Runtime.Serialization;
+
+namespace WebSocketSharp.Net
+{
+ ///
+ /// The exception that is thrown when an error occurs processing
+ /// an HTTP request.
+ ///
+ [Serializable]
+ public class HttpListenerException : Win32Exception
+ {
+ #region Protected Constructors
+
+ ///
+ /// Initializes a new instance of the
+ /// class with the specified serialized data.
+ ///
+ ///
+ /// A that contains the serialized
+ /// object data.
+ ///
+ ///
+ /// A that specifies the source for
+ /// the deserialization.
+ ///
+ ///
+ /// is .
+ ///
+ protected HttpListenerException (
+ SerializationInfo serializationInfo,
+ StreamingContext streamingContext
+ )
+ : base (serializationInfo, streamingContext)
+ {
+ }
+
+ #endregion
+
+ #region Public Constructors
+
+ ///
+ /// Initializes a new instance of the
+ /// class.
+ ///
+ public HttpListenerException ()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the
+ /// class with the specified error code.
+ ///
+ ///
+ /// An that specifies the error code.
+ ///
+ public HttpListenerException (int errorCode)
+ : base (errorCode)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the
+ /// class with the specified error code and message.
+ ///
+ ///
+ /// An that specifies the error code.
+ ///
+ ///
+ /// A that specifies the message.
+ ///
+ public HttpListenerException (int errorCode, string message)
+ : base (errorCode, message)
+ {
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the error code that identifies the error that occurred.
+ ///
+ ///
+ ///
+ /// An that represents the error code.
+ ///
+ ///
+ /// It is any of the Win32 error codes.
+ ///
+ ///
+ public override int ErrorCode {
+ get {
+ return NativeErrorCode;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/HttpListenerException.cs.meta b/Assets/External/websocket-sharp/Net/HttpListenerException.cs.meta
new file mode 100644
index 00000000..346f3aa5
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpListenerException.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 07dd63c18a8033d40a377a29d705ff58
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/HttpListenerPrefix.cs b/Assets/External/websocket-sharp/Net/HttpListenerPrefix.cs
new file mode 100644
index 00000000..aba9d1bf
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpListenerPrefix.cs
@@ -0,0 +1,243 @@
+#region License
+/*
+ * HttpListenerPrefix.cs
+ *
+ * This code is derived from ListenerPrefix.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2012-2024 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ * - Oleg Mihailik
+ */
+#endregion
+
+using System;
+
+namespace WebSocketSharp.Net
+{
+ internal sealed class HttpListenerPrefix
+ {
+ #region Private Fields
+
+ private string _host;
+ private bool _isSecure;
+ private HttpListener _listener;
+ private string _original;
+ private string _path;
+ private string _port;
+ private string _prefix;
+ private string _scheme;
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal HttpListenerPrefix (string uriPrefix, HttpListener listener)
+ {
+ _original = uriPrefix;
+ _listener = listener;
+
+ parse (uriPrefix);
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ public string Host {
+ get {
+ return _host;
+ }
+ }
+
+ public bool IsSecure {
+ get {
+ return _isSecure;
+ }
+ }
+
+ public HttpListener Listener {
+ get {
+ return _listener;
+ }
+ }
+
+ public string Original {
+ get {
+ return _original;
+ }
+ }
+
+ public string Path {
+ get {
+ return _path;
+ }
+ }
+
+ public string Port {
+ get {
+ return _port;
+ }
+ }
+
+ public string Scheme {
+ get {
+ return _scheme;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private void parse (string uriPrefix)
+ {
+ var compType = StringComparison.Ordinal;
+
+ _isSecure = uriPrefix.StartsWith ("https", compType);
+ _scheme = _isSecure ? "https" : "http";
+
+ var hostStartIdx = uriPrefix.IndexOf (':') + 3;
+
+ var len = uriPrefix.Length;
+ var rootIdx = uriPrefix
+ .IndexOf ('/', hostStartIdx + 1, len - hostStartIdx - 1);
+
+ var colonIdx = uriPrefix
+ .LastIndexOf (':', rootIdx - 1, rootIdx - hostStartIdx - 1);
+
+ var hasPort = uriPrefix[rootIdx - 1] != ']' && colonIdx > hostStartIdx;
+
+ if (hasPort) {
+ _host = uriPrefix.Substring (hostStartIdx, colonIdx - hostStartIdx);
+ _port = uriPrefix.Substring (colonIdx + 1, rootIdx - colonIdx - 1);
+ }
+ else {
+ _host = uriPrefix.Substring (hostStartIdx, rootIdx - hostStartIdx);
+ _port = _isSecure ? "443" : "80";
+ }
+
+ _path = uriPrefix.Substring (rootIdx);
+
+ var fmt = "{0}://{1}:{2}{3}";
+
+ _prefix = String.Format (fmt, _scheme, _host, _port, _path);
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public static void CheckPrefix (string uriPrefix)
+ {
+ if (uriPrefix == null)
+ throw new ArgumentNullException ("uriPrefix");
+
+ var len = uriPrefix.Length;
+
+ if (len == 0) {
+ var msg = "An empty string.";
+
+ throw new ArgumentException (msg, "uriPrefix");
+ }
+
+ var compType = StringComparison.Ordinal;
+ var isHttpSchm = uriPrefix.StartsWith ("http://", compType)
+ || uriPrefix.StartsWith ("https://", compType);
+
+ if (!isHttpSchm) {
+ var msg = "The scheme is not http or https.";
+
+ throw new ArgumentException (msg, "uriPrefix");
+ }
+
+ var endIdx = len - 1;
+
+ if (uriPrefix[endIdx] != '/') {
+ var msg = "It ends without a forward slash.";
+
+ throw new ArgumentException (msg, "uriPrefix");
+ }
+
+ var hostStartIdx = uriPrefix.IndexOf (':') + 3;
+
+ if (hostStartIdx >= endIdx) {
+ var msg = "No host is specified.";
+
+ throw new ArgumentException (msg, "uriPrefix");
+ }
+
+ if (uriPrefix[hostStartIdx] == ':') {
+ var msg = "No host is specified.";
+
+ throw new ArgumentException (msg, "uriPrefix");
+ }
+
+ var rootIdx = uriPrefix.IndexOf ('/', hostStartIdx, len - hostStartIdx);
+
+ if (rootIdx == hostStartIdx) {
+ var msg = "No host is specified.";
+
+ throw new ArgumentException (msg, "uriPrefix");
+ }
+
+ if (uriPrefix[rootIdx - 1] == ':') {
+ var msg = "No port is specified.";
+
+ throw new ArgumentException (msg, "uriPrefix");
+ }
+
+ if (rootIdx == endIdx - 1) {
+ var msg = "No path is specified.";
+
+ throw new ArgumentException (msg, "uriPrefix");
+ }
+ }
+
+ public override bool Equals (object obj)
+ {
+ var pref = obj as HttpListenerPrefix;
+
+ return pref != null && _prefix.Equals (pref._prefix);
+ }
+
+ public override int GetHashCode ()
+ {
+ return _prefix.GetHashCode ();
+ }
+
+ public override string ToString ()
+ {
+ return _prefix;
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/HttpListenerPrefix.cs.meta b/Assets/External/websocket-sharp/Net/HttpListenerPrefix.cs.meta
new file mode 100644
index 00000000..95df4db5
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpListenerPrefix.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 9da8d497d19f7f745826de72945261d6
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/HttpListenerPrefixCollection.cs b/Assets/External/websocket-sharp/Net/HttpListenerPrefixCollection.cs
new file mode 100644
index 00000000..3f5c44a2
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpListenerPrefixCollection.cs
@@ -0,0 +1,295 @@
+#region License
+/*
+ * HttpListenerPrefixCollection.cs
+ *
+ * This code is derived from HttpListenerPrefixCollection.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2012-2024 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace WebSocketSharp.Net
+{
+ ///
+ /// Provides a collection used to store the URI prefixes for a instance of
+ /// the class.
+ ///
+ ///
+ /// The instance responds to the request which
+ /// has a requested URI that the prefixes most closely match.
+ ///
+ public class HttpListenerPrefixCollection : ICollection
+ {
+ #region Private Fields
+
+ private HttpListener _listener;
+ private List _prefixes;
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal HttpListenerPrefixCollection (HttpListener listener)
+ {
+ _listener = listener;
+
+ _prefixes = new List ();
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the number of prefixes in the collection.
+ ///
+ ///
+ /// An that represents the number of prefixes.
+ ///
+ public int Count {
+ get {
+ return _prefixes.Count;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the access to the collection is
+ /// read-only.
+ ///
+ ///
+ /// Always returns false.
+ ///
+ public bool IsReadOnly {
+ get {
+ return false;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the access to the collection is
+ /// synchronized.
+ ///
+ ///
+ /// Always returns false.
+ ///
+ public bool IsSynchronized {
+ get {
+ return false;
+ }
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Adds the specified URI prefix to the collection.
+ ///
+ ///
+ ///
+ /// A that specifies the URI prefix to add.
+ ///
+ ///
+ /// It must be a well-formed URI prefix with http or https scheme,
+ /// and must end with a forward slash (/).
+ ///
+ ///
+ ///
+ /// is invalid.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The instance associated with this
+ /// collection is closed.
+ ///
+ public void Add (string uriPrefix)
+ {
+ _listener.CheckDisposed ();
+
+ HttpListenerPrefix.CheckPrefix (uriPrefix);
+
+ if (_prefixes.Contains (uriPrefix))
+ return;
+
+ if (_listener.IsListening)
+ EndPointManager.AddPrefix (uriPrefix, _listener);
+
+ _prefixes.Add (uriPrefix);
+ }
+
+ ///
+ /// Removes all URI prefixes from the collection.
+ ///
+ ///
+ /// The instance associated with this
+ /// collection is closed.
+ ///
+ public void Clear ()
+ {
+ _listener.CheckDisposed ();
+
+ if (_listener.IsListening)
+ EndPointManager.RemoveListener (_listener);
+
+ _prefixes.Clear ();
+ }
+
+ ///
+ /// Returns a value indicating whether the collection contains the
+ /// specified URI prefix.
+ ///
+ ///
+ /// true if the collection contains the URI prefix; otherwise,
+ /// false.
+ ///
+ ///
+ /// A that specifies the URI prefix to test.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The instance associated with this
+ /// collection is closed.
+ ///
+ public bool Contains (string uriPrefix)
+ {
+ _listener.CheckDisposed ();
+
+ if (uriPrefix == null)
+ throw new ArgumentNullException ("uriPrefix");
+
+ return _prefixes.Contains (uriPrefix);
+ }
+
+ ///
+ /// Copies the contents of the collection to the specified array of string.
+ ///
+ ///
+ /// An array of that specifies the destination of
+ /// the URI prefix strings copied from the collection.
+ ///
+ ///
+ /// An that specifies the zero-based index in
+ /// the array at which copying begins.
+ ///
+ ///
+ /// The space from to the end of
+ /// is not enough to copy to.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// is less than zero.
+ ///
+ ///
+ /// The instance associated with this
+ /// collection is closed.
+ ///
+ public void CopyTo (string[] array, int offset)
+ {
+ _listener.CheckDisposed ();
+
+ _prefixes.CopyTo (array, offset);
+ }
+
+ ///
+ /// Gets the enumerator that iterates through the collection.
+ ///
+ ///
+ /// An
+ /// instance that can be used to iterate through the collection.
+ ///
+ public IEnumerator GetEnumerator ()
+ {
+ return _prefixes.GetEnumerator ();
+ }
+
+ ///
+ /// Removes the specified URI prefix from the collection.
+ ///
+ ///
+ /// true if the URI prefix is successfully removed; otherwise,
+ /// false.
+ ///
+ ///
+ /// A that specifies the URI prefix to remove.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The instance associated with this
+ /// collection is closed.
+ ///
+ public bool Remove (string uriPrefix)
+ {
+ _listener.CheckDisposed ();
+
+ if (uriPrefix == null)
+ throw new ArgumentNullException ("uriPrefix");
+
+ if (!_prefixes.Contains (uriPrefix))
+ return false;
+
+ if (_listener.IsListening)
+ EndPointManager.RemovePrefix (uriPrefix, _listener);
+
+ return _prefixes.Remove (uriPrefix);
+ }
+
+ #endregion
+
+ #region Explicit Interface Implementations
+
+ ///
+ /// Gets the enumerator that iterates through the collection.
+ ///
+ ///
+ /// An instance that can be used to iterate
+ /// through the collection.
+ ///
+ IEnumerator IEnumerable.GetEnumerator ()
+ {
+ return _prefixes.GetEnumerator ();
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/HttpListenerPrefixCollection.cs.meta b/Assets/External/websocket-sharp/Net/HttpListenerPrefixCollection.cs.meta
new file mode 100644
index 00000000..f0a9ba80
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpListenerPrefixCollection.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 9940f054c5d5b5240a1491e9c6c82159
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/HttpListenerRequest.cs b/Assets/External/websocket-sharp/Net/HttpListenerRequest.cs
new file mode 100644
index 00000000..9c7b1a16
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpListenerRequest.cs
@@ -0,0 +1,943 @@
+#region License
+/*
+ * HttpListenerRequest.cs
+ *
+ * This code is derived from HttpListenerRequest.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2012-2024 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Globalization;
+using System.IO;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+
+namespace WebSocketSharp.Net
+{
+ ///
+ /// Represents an incoming HTTP request to a
+ /// instance.
+ ///
+ ///
+ /// This class cannot be inherited.
+ ///
+ public sealed class HttpListenerRequest
+ {
+ #region Private Fields
+
+ private static readonly byte[] _100continue;
+ private string[] _acceptTypes;
+ private bool _chunked;
+ private HttpConnection _connection;
+ private Encoding _contentEncoding;
+ private long _contentLength;
+ private HttpListenerContext _context;
+ private CookieCollection _cookies;
+ private static readonly Encoding _defaultEncoding;
+ private WebHeaderCollection _headers;
+ private string _httpMethod;
+ private Stream _inputStream;
+ private Version _protocolVersion;
+ private NameValueCollection _queryString;
+ private string _rawUrl;
+ private Guid _requestTraceIdentifier;
+ private Uri _url;
+ private Uri _urlReferrer;
+ private bool _urlSet;
+ private string _userHostName;
+ private string[] _userLanguages;
+
+ #endregion
+
+ #region Static Constructor
+
+ static HttpListenerRequest ()
+ {
+ _100continue = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n");
+ _defaultEncoding = Encoding.UTF8;
+ }
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal HttpListenerRequest (HttpListenerContext context)
+ {
+ _context = context;
+
+ _connection = context.Connection;
+ _contentLength = -1;
+ _headers = new WebHeaderCollection ();
+ _requestTraceIdentifier = Guid.NewGuid ();
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the media types that are acceptable for the client.
+ ///
+ ///
+ ///
+ /// An array of that contains the names of
+ /// the media types specified in the value of the Accept header.
+ ///
+ ///
+ /// if the header is not present.
+ ///
+ ///
+ public string[] AcceptTypes {
+ get {
+ var val = _headers["Accept"];
+
+ if (val == null)
+ return null;
+
+ if (_acceptTypes == null) {
+ _acceptTypes = val
+ .SplitHeaderValue (',')
+ .TrimEach ()
+ .ToList ()
+ .ToArray ();
+ }
+
+ return _acceptTypes;
+ }
+ }
+
+ ///
+ /// Gets an error code that identifies a problem with the certificate
+ /// provided by the client.
+ ///
+ ///
+ /// An that represents an error code.
+ ///
+ ///
+ /// This property is not supported.
+ ///
+ public int ClientCertificateError {
+ get {
+ throw new NotSupportedException ();
+ }
+ }
+
+ ///
+ /// Gets the encoding for the entity body data included in the request.
+ ///
+ ///
+ ///
+ /// A converted from the charset value of the
+ /// Content-Type header.
+ ///
+ ///
+ /// if the charset value is not available.
+ ///
+ ///
+ public Encoding ContentEncoding {
+ get {
+ if (_contentEncoding == null)
+ _contentEncoding = getContentEncoding ();
+
+ return _contentEncoding;
+ }
+ }
+
+ ///
+ /// Gets the length in bytes of the entity body data included in the
+ /// request.
+ ///
+ ///
+ ///
+ /// A converted from the value of the Content-Length
+ /// header.
+ ///
+ ///
+ /// -1 if the header is not present.
+ ///
+ ///
+ public long ContentLength64 {
+ get {
+ return _contentLength;
+ }
+ }
+
+ ///
+ /// Gets the media type of the entity body data included in the request.
+ ///
+ ///
+ ///
+ /// A that represents the value of the Content-Type
+ /// header.
+ ///
+ ///
+ /// if the header is not present.
+ ///
+ ///
+ public string ContentType {
+ get {
+ return _headers["Content-Type"];
+ }
+ }
+
+ ///
+ /// Gets the HTTP cookies included in the request.
+ ///
+ ///
+ ///
+ /// A that contains the cookies.
+ ///
+ ///
+ /// An empty collection if not included.
+ ///
+ ///
+ public CookieCollection Cookies {
+ get {
+ if (_cookies == null)
+ _cookies = _headers.GetCookies (false);
+
+ return _cookies;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the request has the entity body data.
+ ///
+ ///
+ /// true if the request has the entity body data; otherwise,
+ /// false.
+ ///
+ public bool HasEntityBody {
+ get {
+ return _contentLength > 0 || _chunked;
+ }
+ }
+
+ ///
+ /// Gets the HTTP headers included in the request.
+ ///
+ ///
+ /// A that contains the headers.
+ ///
+ public NameValueCollection Headers {
+ get {
+ return _headers;
+ }
+ }
+
+ ///
+ /// Gets the HTTP method specified by the client.
+ ///
+ ///
+ /// A that represents the HTTP method specified in
+ /// the request line.
+ ///
+ public string HttpMethod {
+ get {
+ return _httpMethod;
+ }
+ }
+
+ ///
+ /// Gets a stream that contains the entity body data included in
+ /// the request.
+ ///
+ ///
+ ///
+ /// A that contains the entity body data.
+ ///
+ ///
+ /// if the entity body data is not available.
+ ///
+ ///
+ public Stream InputStream {
+ get {
+ if (_inputStream == null) {
+ _inputStream = _contentLength > 0 || _chunked
+ ? _connection
+ .GetRequestStream (_contentLength, _chunked)
+ : Stream.Null;
+ }
+
+ return _inputStream;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the client is authenticated.
+ ///
+ ///
+ /// true if the client is authenticated; otherwise, false.
+ ///
+ public bool IsAuthenticated {
+ get {
+ return _context.User != null;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the request is sent from the
+ /// local computer.
+ ///
+ ///
+ /// true if the request is sent from the same computer as
+ /// the server; otherwise, false.
+ ///
+ public bool IsLocal {
+ get {
+ return _connection.IsLocal;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether a secure connection is used to send
+ /// the request.
+ ///
+ ///
+ /// true if the connection is secure; otherwise, false.
+ ///
+ public bool IsSecureConnection {
+ get {
+ return _connection.IsSecure;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the request is a WebSocket handshake
+ /// request.
+ ///
+ ///
+ /// true if the request is a WebSocket handshake request; otherwise,
+ /// false.
+ ///
+ public bool IsWebSocketRequest {
+ get {
+ return _httpMethod == "GET" && _headers.Upgrades ("websocket");
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether a persistent connection is requested.
+ ///
+ ///
+ /// true if the request specifies that the connection is kept open;
+ /// otherwise, false.
+ ///
+ public bool KeepAlive {
+ get {
+ return _headers.KeepsAlive (_protocolVersion);
+ }
+ }
+
+ ///
+ /// Gets the endpoint to which the request is sent.
+ ///
+ ///
+ /// A that represents the server
+ /// IP address and port number.
+ ///
+ public System.Net.IPEndPoint LocalEndPoint {
+ get {
+ return _connection.LocalEndPoint;
+ }
+ }
+
+ ///
+ /// Gets the HTTP version specified by the client.
+ ///
+ ///
+ /// A that represents the HTTP version specified in
+ /// the request line.
+ ///
+ public Version ProtocolVersion {
+ get {
+ return _protocolVersion;
+ }
+ }
+
+ ///
+ /// Gets the query string included in the request.
+ ///
+ ///
+ ///
+ /// A that contains the query
+ /// parameters.
+ ///
+ ///
+ /// Each query parameter is decoded in UTF-8.
+ ///
+ ///
+ /// An empty collection if not included.
+ ///
+ ///
+ public NameValueCollection QueryString {
+ get {
+ if (_queryString == null) {
+ var url = Url;
+ var query = url != null ? url.Query : null;
+
+ _queryString = QueryStringCollection.Parse (query, _defaultEncoding);
+ }
+
+ return _queryString;
+ }
+ }
+
+ ///
+ /// Gets the raw URL specified by the client.
+ ///
+ ///
+ /// A that represents the request target specified in
+ /// the request line.
+ ///
+ public string RawUrl {
+ get {
+ return _rawUrl;
+ }
+ }
+
+ ///
+ /// Gets the endpoint from which the request is sent.
+ ///
+ ///
+ /// A that represents the client
+ /// IP address and port number.
+ ///
+ public System.Net.IPEndPoint RemoteEndPoint {
+ get {
+ return _connection.RemoteEndPoint;
+ }
+ }
+
+ ///
+ /// Gets the trace identifier of the request.
+ ///
+ ///
+ /// A that represents the trace identifier.
+ ///
+ public Guid RequestTraceIdentifier {
+ get {
+ return _requestTraceIdentifier;
+ }
+ }
+
+ ///
+ /// Gets the URL requested by the client.
+ ///
+ ///
+ ///
+ /// A that represents the URL parsed from the request.
+ ///
+ ///
+ /// if the URL cannot be parsed.
+ ///
+ ///
+ public Uri Url {
+ get {
+ if (!_urlSet) {
+ _url = HttpUtility
+ .CreateRequestUrl (
+ _rawUrl,
+ _userHostName,
+ IsWebSocketRequest,
+ IsSecureConnection
+ );
+
+ _urlSet = true;
+ }
+
+ return _url;
+ }
+ }
+
+ ///
+ /// Gets the URI of the resource from which the requested URL was obtained.
+ ///
+ ///
+ ///
+ /// A that represents the value of the Referer header.
+ ///
+ ///
+ /// if the header value is not available.
+ ///
+ ///
+ public Uri UrlReferrer {
+ get {
+ var val = _headers["Referer"];
+
+ if (val == null)
+ return null;
+
+ if (_urlReferrer == null)
+ _urlReferrer = val.ToUri ();
+
+ return _urlReferrer;
+ }
+ }
+
+ ///
+ /// Gets the user agent from which the request is originated.
+ ///
+ ///
+ ///
+ /// A that represents the value of the User-Agent
+ /// header.
+ ///
+ ///
+ /// if the header is not present.
+ ///
+ ///
+ public string UserAgent {
+ get {
+ return _headers["User-Agent"];
+ }
+ }
+
+ ///
+ /// Gets the IP address and port number to which the request is sent.
+ ///
+ ///
+ /// A that represents the server IP address and
+ /// port number.
+ ///
+ public string UserHostAddress {
+ get {
+ return _connection.LocalEndPoint.ToString ();
+ }
+ }
+
+ ///
+ /// Gets the server host name requested by the client.
+ ///
+ ///
+ ///
+ /// A that represents the value of the Host header.
+ ///
+ ///
+ /// It includes the port number if provided.
+ ///
+ ///
+ public string UserHostName {
+ get {
+ return _userHostName;
+ }
+ }
+
+ ///
+ /// Gets the natural languages that are acceptable for the client.
+ ///
+ ///
+ ///
+ /// An array of that contains the names of the
+ /// natural languages specified in the value of the Accept-Language
+ /// header.
+ ///
+ ///
+ /// if the header is not present.
+ ///
+ ///
+ public string[] UserLanguages {
+ get {
+ var val = _headers["Accept-Language"];
+
+ if (val == null)
+ return null;
+
+ if (_userLanguages == null)
+ _userLanguages = val.Split (',').TrimEach ().ToList ().ToArray ();
+
+ return _userLanguages;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private Encoding getContentEncoding ()
+ {
+ var val = _headers["Content-Type"];
+
+ if (val == null)
+ return _defaultEncoding;
+
+ Encoding ret;
+
+ return HttpUtility.TryGetEncoding (val, out ret)
+ ? ret
+ : _defaultEncoding;
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal void AddHeader (string headerField)
+ {
+ var start = headerField[0];
+
+ if (start == ' ' || start == '\t') {
+ _context.ErrorMessage = "Invalid header field";
+
+ return;
+ }
+
+ var colon = headerField.IndexOf (':');
+
+ if (colon < 1) {
+ _context.ErrorMessage = "Invalid header field";
+
+ return;
+ }
+
+ var name = headerField.Substring (0, colon).Trim ();
+
+ if (name.Length == 0 || !name.IsToken ()) {
+ _context.ErrorMessage = "Invalid header name";
+
+ return;
+ }
+
+ var val = colon < headerField.Length - 1
+ ? headerField.Substring (colon + 1).Trim ()
+ : String.Empty;
+
+ _headers.InternalSet (name, val, false);
+
+ var lower = name.ToLower (CultureInfo.InvariantCulture);
+
+ if (lower == "host") {
+ if (_userHostName != null) {
+ _context.ErrorMessage = "Invalid Host header";
+
+ return;
+ }
+
+ if (val.Length == 0) {
+ _context.ErrorMessage = "Invalid Host header";
+
+ return;
+ }
+
+ _userHostName = val;
+
+ return;
+ }
+
+ if (lower == "content-length") {
+ if (_contentLength > -1) {
+ _context.ErrorMessage = "Invalid Content-Length header";
+
+ return;
+ }
+
+ long len;
+
+ if (!Int64.TryParse (val, out len)) {
+ _context.ErrorMessage = "Invalid Content-Length header";
+
+ return;
+ }
+
+ if (len < 0) {
+ _context.ErrorMessage = "Invalid Content-Length header";
+
+ return;
+ }
+
+ _contentLength = len;
+
+ return;
+ }
+ }
+
+ internal void FinishInitialization ()
+ {
+ if (_userHostName == null) {
+ _context.ErrorMessage = "Host header required";
+
+ return;
+ }
+
+ var transferEnc = _headers["Transfer-Encoding"];
+
+ if (transferEnc != null) {
+ var compType = StringComparison.OrdinalIgnoreCase;
+
+ if (!transferEnc.Equals ("chunked", compType)) {
+ _context.ErrorStatusCode = 501;
+ _context.ErrorMessage = "Invalid Transfer-Encoding header";
+
+ return;
+ }
+
+ _chunked = true;
+ }
+
+ if (_httpMethod == "POST" || _httpMethod == "PUT") {
+ if (_contentLength == -1 && !_chunked) {
+ _context.ErrorStatusCode = 411;
+ _context.ErrorMessage = "Content-Length header required";
+
+ return;
+ }
+
+ if (_contentLength == 0 && !_chunked) {
+ _context.ErrorStatusCode = 411;
+ _context.ErrorMessage = "Invalid Content-Length header";
+
+ return;
+ }
+ }
+
+ var expect = _headers["Expect"];
+
+ if (expect != null) {
+ var compType = StringComparison.OrdinalIgnoreCase;
+
+ if (!expect.Equals ("100-continue", compType)) {
+ _context.ErrorStatusCode = 417;
+ _context.ErrorMessage = "Invalid Expect header";
+
+ return;
+ }
+
+ var output = _connection.GetResponseStream ();
+
+ output.InternalWrite (_100continue, 0, _100continue.Length);
+ }
+ }
+
+ internal bool FlushInput ()
+ {
+ var input = InputStream;
+
+ if (input == Stream.Null)
+ return true;
+
+ var len = 2048;
+
+ if (_contentLength > 0 && _contentLength < len)
+ len = (int) _contentLength;
+
+ var buff = new byte[len];
+
+ while (true) {
+ try {
+ var ares = input.BeginRead (buff, 0, len, null, null);
+
+ if (!ares.IsCompleted) {
+ var timeout = 100;
+
+ if (!ares.AsyncWaitHandle.WaitOne (timeout))
+ return false;
+ }
+
+ if (input.EndRead (ares) <= 0)
+ return true;
+ }
+ catch {
+ return false;
+ }
+ }
+ }
+
+ internal bool IsUpgradeRequest (string protocol)
+ {
+ return _headers.Upgrades (protocol);
+ }
+
+ internal void SetRequestLine (string requestLine)
+ {
+ var parts = requestLine.Split (new[] { ' ' }, 3);
+
+ if (parts.Length < 3) {
+ _context.ErrorMessage = "Invalid request line (parts)";
+
+ return;
+ }
+
+ var method = parts[0];
+
+ if (method.Length == 0) {
+ _context.ErrorMessage = "Invalid request line (method)";
+
+ return;
+ }
+
+ if (!method.IsHttpMethod ()) {
+ _context.ErrorStatusCode = 501;
+ _context.ErrorMessage = "Invalid request line (method)";
+
+ return;
+ }
+
+ var target = parts[1];
+
+ if (target.Length == 0) {
+ _context.ErrorMessage = "Invalid request line (target)";
+
+ return;
+ }
+
+ var rawVer = parts[2];
+
+ if (rawVer.Length != 8) {
+ _context.ErrorMessage = "Invalid request line (version)";
+
+ return;
+ }
+
+ if (!rawVer.StartsWith ("HTTP/", StringComparison.Ordinal)) {
+ _context.ErrorMessage = "Invalid request line (version)";
+
+ return;
+ }
+
+ Version ver;
+
+ if (!rawVer.Substring (5).TryCreateVersion (out ver)) {
+ _context.ErrorMessage = "Invalid request line (version)";
+
+ return;
+ }
+
+ if (ver != HttpVersion.Version11) {
+ _context.ErrorStatusCode = 505;
+ _context.ErrorMessage = "Invalid request line (version)";
+
+ return;
+ }
+
+ _httpMethod = method;
+ _rawUrl = target;
+ _protocolVersion = ver;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Begins getting the certificate provided by the client asynchronously.
+ ///
+ ///
+ /// An instance that represents the status of
+ /// the asynchronous operation.
+ ///
+ ///
+ ///
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the asynchronous operation is
+ /// complete.
+ ///
+ ///
+ ///
+ /// An that specifies a user defined object to pass to
+ /// .
+ ///
+ ///
+ /// This method is not supported.
+ ///
+ public IAsyncResult BeginGetClientCertificate (
+ AsyncCallback requestCallback,
+ object state
+ )
+ {
+ throw new NotSupportedException ();
+ }
+
+ ///
+ /// Ends an asynchronous operation to get the certificate provided by
+ /// the client.
+ ///
+ ///
+ /// A that represents an X.509 certificate
+ /// provided by the client.
+ ///
+ ///
+ /// An instance obtained by calling
+ /// the method.
+ ///
+ ///
+ /// This method is not supported.
+ ///
+ public X509Certificate2 EndGetClientCertificate (IAsyncResult asyncResult)
+ {
+ throw new NotSupportedException ();
+ }
+
+ ///
+ /// Gets the certificate provided by the client.
+ ///
+ ///
+ /// A that represents an X.509 certificate
+ /// provided by the client.
+ ///
+ ///
+ /// This method is not supported.
+ ///
+ public X509Certificate2 GetClientCertificate ()
+ {
+ throw new NotSupportedException ();
+ }
+
+ ///
+ /// Returns a string that represents the current instance.
+ ///
+ ///
+ /// A that contains the request line and headers
+ /// included in the request.
+ ///
+ public override string ToString ()
+ {
+ var buff = new StringBuilder (64);
+
+ var fmt = "{0} {1} HTTP/{2}\r\n";
+ var headers = _headers.ToString ();
+
+ buff
+ .AppendFormat (fmt, _httpMethod, _rawUrl, _protocolVersion)
+ .Append (headers);
+
+ return buff.ToString ();
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/HttpListenerRequest.cs.meta b/Assets/External/websocket-sharp/Net/HttpListenerRequest.cs.meta
new file mode 100644
index 00000000..15f73d28
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpListenerRequest.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 74456f8199f4b2c4aae6bd8089faf59a
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/HttpListenerResponse.cs b/Assets/External/websocket-sharp/Net/HttpListenerResponse.cs
new file mode 100644
index 00000000..500d4282
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpListenerResponse.cs
@@ -0,0 +1,1201 @@
+#region License
+/*
+ * HttpListenerResponse.cs
+ *
+ * This code is derived from HttpListenerResponse.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2012-2024 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+#region Contributors
+/*
+ * Contributors:
+ * - Nicholas Devenish
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Text;
+
+namespace WebSocketSharp.Net
+{
+ ///
+ /// Represents an HTTP response to an HTTP request received by
+ /// a instance.
+ ///
+ ///
+ /// This class cannot be inherited.
+ ///
+ public sealed class HttpListenerResponse : IDisposable
+ {
+ #region Private Fields
+
+ private bool _closeConnection;
+ private Encoding _contentEncoding;
+ private long _contentLength;
+ private string _contentType;
+ private HttpListenerContext _context;
+ private CookieCollection _cookies;
+ private static readonly string _defaultProductName;
+ private bool _disposed;
+ private WebHeaderCollection _headers;
+ private bool _headersSent;
+ private bool _keepAlive;
+ private ResponseStream _outputStream;
+ private Uri _redirectLocation;
+ private bool _sendChunked;
+ private int _statusCode;
+ private string _statusDescription;
+ private Version _version;
+
+ #endregion
+
+ #region Static Constructor
+
+ static HttpListenerResponse ()
+ {
+ _defaultProductName = "websocket-sharp/1.0";
+ }
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal HttpListenerResponse (HttpListenerContext context)
+ {
+ _context = context;
+
+ _keepAlive = true;
+ _statusCode = 200;
+ _statusDescription = "OK";
+ _version = HttpVersion.Version11;
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal bool CloseConnection {
+ get {
+ return _closeConnection;
+ }
+
+ set {
+ _closeConnection = value;
+ }
+ }
+
+ internal WebHeaderCollection FullHeaders {
+ get {
+ var headers = new WebHeaderCollection (HttpHeaderType.Response, true);
+
+ if (_headers != null)
+ headers.Add (_headers);
+
+ if (_contentType != null) {
+ var val = createContentTypeHeaderText (_contentType, _contentEncoding);
+
+ headers.InternalSet ("Content-Type", val, true);
+ }
+
+ if (headers["Server"] == null)
+ headers.InternalSet ("Server", _defaultProductName, true);
+
+ if (headers["Date"] == null) {
+ var val = DateTime.UtcNow.ToString ("r", CultureInfo.InvariantCulture);
+
+ headers.InternalSet ("Date", val, true);
+ }
+
+ if (_sendChunked) {
+ headers.InternalSet ("Transfer-Encoding", "chunked", true);
+ }
+ else {
+ var val = _contentLength.ToString (CultureInfo.InvariantCulture);
+
+ headers.InternalSet ("Content-Length", val, true);
+ }
+
+ /*
+ * Apache forces closing the connection for these status codes:
+ * - 400 Bad Request
+ * - 408 Request Timeout
+ * - 411 Length Required
+ * - 413 Request Entity Too Large
+ * - 414 Request-Uri Too Long
+ * - 500 Internal Server Error
+ * - 503 Service Unavailable
+ */
+
+ var reuses = _context.Connection.Reuses;
+ var closeConn = !_context.Request.KeepAlive
+ || !_keepAlive
+ || reuses >= 100
+ || _statusCode == 400
+ || _statusCode == 408
+ || _statusCode == 411
+ || _statusCode == 413
+ || _statusCode == 414
+ || _statusCode == 500
+ || _statusCode == 503;
+
+ if (closeConn) {
+ headers.InternalSet ("Connection", "close", true);
+ }
+ else {
+ var fmt = "timeout=15,max={0}";
+ var max = 100 - reuses;
+ var val = String.Format (fmt, max);
+
+ headers.InternalSet ("Keep-Alive", val, true);
+ }
+
+ if (_redirectLocation != null)
+ headers.InternalSet ("Location", _redirectLocation.AbsoluteUri, true);
+
+ if (_cookies != null) {
+ foreach (var cookie in _cookies) {
+ var val = cookie.ToResponseString ();
+
+ headers.InternalSet ("Set-Cookie", val, true);
+ }
+ }
+
+ return headers;
+ }
+ }
+
+ internal bool HeadersSent {
+ get {
+ return _headersSent;
+ }
+
+ set {
+ _headersSent = value;
+ }
+ }
+
+ internal string ObjectName {
+ get {
+ return GetType ().ToString ();
+ }
+ }
+
+ internal string StatusLine {
+ get {
+ var fmt = "HTTP/{0} {1} {2}\r\n";
+
+ return String.Format (fmt, _version, _statusCode, _statusDescription);
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets or sets the encoding for the entity body data included in
+ /// the response.
+ ///
+ ///
+ ///
+ /// A that represents the encoding for
+ /// the entity body data.
+ ///
+ ///
+ /// if no encoding is specified.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ ///
+ /// The response is already being sent.
+ ///
+ ///
+ /// This instance is closed.
+ ///
+ public Encoding ContentEncoding {
+ get {
+ return _contentEncoding;
+ }
+
+ set {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (_headersSent) {
+ var msg = "The response is already being sent.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _contentEncoding = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the number of bytes in the entity body data included in
+ /// the response.
+ ///
+ ///
+ ///
+ /// A that represents the number of bytes in
+ /// the entity body data.
+ ///
+ ///
+ /// It is used for the value of the Content-Length header.
+ ///
+ ///
+ /// The default value is zero.
+ ///
+ ///
+ ///
+ /// The value specified for a set operation is less than zero.
+ ///
+ ///
+ /// The response is already being sent.
+ ///
+ ///
+ /// This instance is closed.
+ ///
+ public long ContentLength64 {
+ get {
+ return _contentLength;
+ }
+
+ set {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (_headersSent) {
+ var msg = "The response is already being sent.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (value < 0) {
+ var msg = "Less than zero.";
+
+ throw new ArgumentOutOfRangeException (msg, "value");
+ }
+
+ _contentLength = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the media type of the entity body included in
+ /// the response.
+ ///
+ ///
+ ///
+ /// A that represents the media type of
+ /// the entity body.
+ ///
+ ///
+ /// It is used for the value of the Content-Type header.
+ ///
+ ///
+ /// if no media type is specified.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ ///
+ ///
+ /// The value specified for a set operation is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The value specified for a set operation contains
+ /// an invalid character.
+ ///
+ ///
+ ///
+ /// The response is already being sent.
+ ///
+ ///
+ /// This instance is closed.
+ ///
+ public string ContentType {
+ get {
+ return _contentType;
+ }
+
+ set {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (_headersSent) {
+ var msg = "The response is already being sent.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (value == null) {
+ _contentType = null;
+
+ return;
+ }
+
+ if (value.Length == 0)
+ throw new ArgumentException ("An empty string.", "value");
+
+ if (!isValidForContentType (value)) {
+ var msg = "It contains an invalid character.";
+
+ throw new ArgumentException (msg, "value");
+ }
+
+ _contentType = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the collection of the HTTP cookies sent with the response.
+ ///
+ ///
+ /// A that contains the cookies sent with
+ /// the response.
+ ///
+ public CookieCollection Cookies {
+ get {
+ if (_cookies == null)
+ _cookies = new CookieCollection ();
+
+ return _cookies;
+ }
+
+ set {
+ _cookies = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the collection of the HTTP headers sent to the client.
+ ///
+ ///
+ /// A that contains the headers sent to
+ /// the client.
+ ///
+ ///
+ /// The value specified for a set operation is not valid for a response.
+ ///
+ public WebHeaderCollection Headers {
+ get {
+ if (_headers == null)
+ _headers = new WebHeaderCollection (HttpHeaderType.Response, false);
+
+ return _headers;
+ }
+
+ set {
+ if (value == null) {
+ _headers = null;
+
+ return;
+ }
+
+ if (value.State != HttpHeaderType.Response) {
+ var msg = "The value is not valid for a response.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _headers = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the server requests
+ /// a persistent connection.
+ ///
+ ///
+ ///
+ /// true if the server requests a persistent connection;
+ /// otherwise, false.
+ ///
+ ///
+ /// The default value is true.
+ ///
+ ///
+ ///
+ /// The response is already being sent.
+ ///
+ ///
+ /// This instance is closed.
+ ///
+ public bool KeepAlive {
+ get {
+ return _keepAlive;
+ }
+
+ set {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (_headersSent) {
+ var msg = "The response is already being sent.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _keepAlive = value;
+ }
+ }
+
+ ///
+ /// Gets a stream instance to which the entity body data can be written.
+ ///
+ ///
+ /// A instance to which the entity body data can be
+ /// written.
+ ///
+ ///
+ /// This instance is closed.
+ ///
+ public Stream OutputStream {
+ get {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (_outputStream == null)
+ _outputStream = _context.Connection.GetResponseStream ();
+
+ return _outputStream;
+ }
+ }
+
+ ///
+ /// Gets the HTTP version used for the response.
+ ///
+ ///
+ ///
+ /// A that represents the HTTP version used for
+ /// the response.
+ ///
+ ///
+ /// Always returns same as 1.1.
+ ///
+ ///
+ public Version ProtocolVersion {
+ get {
+ return _version;
+ }
+ }
+
+ ///
+ /// Gets or sets the URL to which the client is redirected to locate
+ /// a requested resource.
+ ///
+ ///
+ ///
+ /// A that represents the absolute URL for
+ /// the redirect location.
+ ///
+ ///
+ /// It is used for the value of the Location header.
+ ///
+ ///
+ /// if no redirect location is specified.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ ///
+ ///
+ /// The value specified for a set operation is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The value specified for a set operation is not an absolute URL.
+ ///
+ ///
+ ///
+ /// The response is already being sent.
+ ///
+ ///
+ /// This instance is closed.
+ ///
+ public string RedirectLocation {
+ get {
+ return _redirectLocation != null
+ ? _redirectLocation.OriginalString
+ : null;
+ }
+
+ set {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (_headersSent) {
+ var msg = "The response is already being sent.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (value == null) {
+ _redirectLocation = null;
+
+ return;
+ }
+
+ if (value.Length == 0)
+ throw new ArgumentException ("An empty string.", "value");
+
+ Uri uri;
+
+ if (!Uri.TryCreate (value, UriKind.Absolute, out uri)) {
+ var msg = "Not an absolute URL.";
+
+ throw new ArgumentException (msg, "value");
+ }
+
+ _redirectLocation = uri;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the response uses the chunked
+ /// transfer encoding.
+ ///
+ ///
+ ///
+ /// true if the response uses the chunked transfer encoding;
+ /// otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ ///
+ /// The response is already being sent.
+ ///
+ ///
+ /// This instance is closed.
+ ///
+ public bool SendChunked {
+ get {
+ return _sendChunked;
+ }
+
+ set {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (_headersSent) {
+ var msg = "The response is already being sent.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _sendChunked = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the HTTP status code returned to the client.
+ ///
+ ///
+ ///
+ /// An that represents the HTTP status code for
+ /// the response to the request.
+ ///
+ ///
+ /// The default value is 200. It indicates that the request has
+ /// succeeded.
+ ///
+ ///
+ ///
+ /// The response is already being sent.
+ ///
+ ///
+ /// This instance is closed.
+ ///
+ ///
+ ///
+ /// The value specified for a set operation is invalid.
+ ///
+ ///
+ /// Valid values are between 100 and 999 inclusive.
+ ///
+ ///
+ public int StatusCode {
+ get {
+ return _statusCode;
+ }
+
+ set {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (_headersSent) {
+ var msg = "The response is already being sent.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (value < 100 || value > 999) {
+ var msg = "A value is not between 100 and 999 inclusive.";
+
+ throw new System.Net.ProtocolViolationException (msg);
+ }
+
+ _statusCode = value;
+ _statusDescription = value.GetStatusDescription ();
+ }
+ }
+
+ ///
+ /// Gets or sets the description of the HTTP status code returned to
+ /// the client.
+ ///
+ ///
+ ///
+ /// A that represents the description of
+ /// the HTTP status code for the response to the request.
+ ///
+ ///
+ /// The default value is
+ /// the
+ /// RFC 2616 description for the
+ /// property value.
+ ///
+ ///
+ /// An empty string if an RFC 2616 description does not exist.
+ ///
+ ///
+ ///
+ /// The value specified for a set operation contains an invalid character.
+ ///
+ ///
+ /// The value specified for a set operation is .
+ ///
+ ///
+ /// The response is already being sent.
+ ///
+ ///
+ /// This instance is closed.
+ ///
+ public string StatusDescription {
+ get {
+ return _statusDescription;
+ }
+
+ set {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (_headersSent) {
+ var msg = "The response is already being sent.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (value == null)
+ throw new ArgumentNullException ("value");
+
+ if (value.Length == 0) {
+ _statusDescription = _statusCode.GetStatusDescription ();
+
+ return;
+ }
+
+ if (!isValidForStatusDescription (value)) {
+ var msg = "It contains an invalid character.";
+
+ throw new ArgumentException (msg, "value");
+ }
+
+ _statusDescription = value;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private bool canSetCookie (Cookie cookie)
+ {
+ var res = findCookie (cookie).ToList ();
+
+ if (res.Count == 0)
+ return true;
+
+ var ver = cookie.Version;
+
+ foreach (var c in res) {
+ if (c.Version == ver)
+ return true;
+ }
+
+ return false;
+ }
+
+ private void close (bool force)
+ {
+ _disposed = true;
+
+ _context.Connection.Close (force);
+ }
+
+ private void close (byte[] responseEntity, int bufferLength, bool willBlock)
+ {
+ if (willBlock) {
+ OutputStream.WriteBytes (responseEntity, bufferLength);
+ close (false);
+
+ return;
+ }
+
+ OutputStream.WriteBytesAsync (
+ responseEntity,
+ bufferLength,
+ () => close (false),
+ null
+ );
+ }
+
+ private static string createContentTypeHeaderText (
+ string value,
+ Encoding encoding
+ )
+ {
+ if (value.Contains ("charset="))
+ return value;
+
+ if (encoding == null)
+ return value;
+
+ var fmt = "{0}; charset={1}";
+
+ return String.Format (fmt, value, encoding.WebName);
+ }
+
+ private IEnumerable findCookie (Cookie cookie)
+ {
+ if (_cookies == null || _cookies.Count == 0)
+ yield break;
+
+ foreach (var c in _cookies) {
+ if (c.EqualsWithoutValueAndVersion (cookie))
+ yield return c;
+ }
+ }
+
+ private static bool isValidForContentType (string value)
+ {
+ foreach (var c in value) {
+ if (c < 0x20)
+ return false;
+
+ if (c > 0x7e)
+ return false;
+
+ if ("()<>@:\\[]?{}".IndexOf (c) > -1)
+ return false;
+ }
+
+ return true;
+ }
+
+ private static bool isValidForStatusDescription (string value)
+ {
+ foreach (var c in value) {
+ if (c < 0x20)
+ return false;
+
+ if (c > 0x7e)
+ return false;
+ }
+
+ return true;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Closes the connection to the client without sending a response.
+ ///
+ public void Abort ()
+ {
+ if (_disposed)
+ return;
+
+ close (true);
+ }
+
+ ///
+ /// Appends an HTTP cookie to the cookies sent with the response.
+ ///
+ ///
+ /// A to append.
+ ///
+ ///
+ /// is .
+ ///
+ public void AppendCookie (Cookie cookie)
+ {
+ Cookies.Add (cookie);
+ }
+
+ ///
+ /// Appends an HTTP header with the specified name and value to
+ /// the headers for the response.
+ ///
+ ///
+ /// A that specifies the name of the header to
+ /// append.
+ ///
+ ///
+ /// A that specifies the value of the header to
+ /// append.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a string of spaces.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a restricted header name.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The length of is greater than 65,535
+ /// characters.
+ ///
+ ///
+ /// The current headers do not allow the header.
+ ///
+ public void AppendHeader (string name, string value)
+ {
+ Headers.Add (name, value);
+ }
+
+ ///
+ /// Sends the response to the client and releases the resources used by
+ /// this instance.
+ ///
+ public void Close ()
+ {
+ if (_disposed)
+ return;
+
+ close (false);
+ }
+
+ ///
+ /// Sends the response with the specified entity body data to the client
+ /// and releases the resources used by this instance.
+ ///
+ ///
+ /// An array of that contains the entity body data.
+ ///
+ ///
+ /// A : true if this method blocks execution while
+ /// flushing the stream to the client; otherwise, false.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// This instance is closed.
+ ///
+ public void Close (byte[] responseEntity, bool willBlock)
+ {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (responseEntity == null)
+ throw new ArgumentNullException ("responseEntity");
+
+ var len = responseEntity.LongLength;
+
+ if (len > Int32.MaxValue) {
+ close (responseEntity, 1024, willBlock);
+
+ return;
+ }
+
+ var stream = OutputStream;
+
+ if (willBlock) {
+ stream.Write (responseEntity, 0, (int) len);
+ close (false);
+
+ return;
+ }
+
+ stream.BeginWrite (
+ responseEntity,
+ 0,
+ (int) len,
+ ar => {
+ stream.EndWrite (ar);
+ close (false);
+ },
+ null
+ );
+ }
+
+ ///
+ /// Copies some properties from the specified response instance to
+ /// this instance.
+ ///
+ ///
+ /// A to copy.
+ ///
+ ///
+ /// is .
+ ///
+ public void CopyFrom (HttpListenerResponse templateResponse)
+ {
+ if (templateResponse == null)
+ throw new ArgumentNullException ("templateResponse");
+
+ var headers = templateResponse._headers;
+
+ if (headers != null) {
+ if (_headers != null)
+ _headers.Clear ();
+
+ Headers.Add (headers);
+ }
+ else {
+ _headers = null;
+ }
+
+ _contentLength = templateResponse._contentLength;
+ _statusCode = templateResponse._statusCode;
+ _statusDescription = templateResponse._statusDescription;
+ _keepAlive = templateResponse._keepAlive;
+ _version = templateResponse._version;
+ }
+
+ ///
+ /// Configures the response to redirect the client's request to
+ /// the specified URL.
+ ///
+ ///
+ /// This method sets the property to
+ /// , the property to
+ /// 302, and the property to "Found".
+ ///
+ ///
+ /// A that specifies the absolute URL to which
+ /// the client is redirected to locate a requested resource.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is not an absolute URL.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The response is already being sent.
+ ///
+ ///
+ /// This instance is closed.
+ ///
+ public void Redirect (string url)
+ {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (_headersSent) {
+ var msg = "The response is already being sent.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (url == null)
+ throw new ArgumentNullException ("url");
+
+ if (url.Length == 0)
+ throw new ArgumentException ("An empty string.", "url");
+
+ Uri uri;
+
+ if (!Uri.TryCreate (url, UriKind.Absolute, out uri)) {
+ var msg = "Not an absolute URL.";
+
+ throw new ArgumentException (msg, "url");
+ }
+
+ _redirectLocation = uri;
+ _statusCode = 302;
+ _statusDescription = "Found";
+ }
+
+ ///
+ /// Adds or updates an HTTP cookie in the cookies sent with the response.
+ ///
+ ///
+ /// A to set.
+ ///
+ ///
+ /// already exists in the cookies but
+ /// it cannot be updated.
+ ///
+ ///
+ /// is .
+ ///
+ public void SetCookie (Cookie cookie)
+ {
+ if (cookie == null)
+ throw new ArgumentNullException ("cookie");
+
+ if (!canSetCookie (cookie)) {
+ var msg = "It cannot be updated.";
+
+ throw new ArgumentException (msg, "cookie");
+ }
+
+ Cookies.Add (cookie);
+ }
+
+ ///
+ /// Adds or updates an HTTP header with the specified name and value in
+ /// the headers for the response.
+ ///
+ ///
+ /// A that specifies the name of the header to set.
+ ///
+ ///
+ /// A that specifies the value of the header to set.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a string of spaces.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a restricted header name.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The length of is greater than 65,535
+ /// characters.
+ ///
+ ///
+ /// The current headers do not allow the header.
+ ///
+ public void SetHeader (string name, string value)
+ {
+ Headers.Set (name, value);
+ }
+
+ #endregion
+
+ #region Explicit Interface Implementations
+
+ ///
+ /// Releases all resources used by this instance.
+ ///
+ void IDisposable.Dispose ()
+ {
+ if (_disposed)
+ return;
+
+ close (true);
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/HttpListenerResponse.cs.meta b/Assets/External/websocket-sharp/Net/HttpListenerResponse.cs.meta
new file mode 100644
index 00000000..507186e0
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpListenerResponse.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 33da38aab487ebd449ffa8848b25d89f
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/HttpRequestHeader.cs b/Assets/External/websocket-sharp/Net/HttpRequestHeader.cs
new file mode 100644
index 00000000..aab3497f
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpRequestHeader.cs
@@ -0,0 +1,233 @@
+#region License
+/*
+ * HttpRequestHeader.cs
+ *
+ * This code is derived from HttpRequestHeader.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2014-2020 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+namespace WebSocketSharp.Net
+{
+ ///
+ /// Indicates the HTTP header that may be specified in a client request.
+ ///
+ ///
+ /// The headers of this enumeration are defined in
+ /// RFC 2616 or
+ /// RFC 6455.
+ ///
+ public enum HttpRequestHeader
+ {
+ ///
+ /// Indicates the Cache-Control header.
+ ///
+ CacheControl,
+ ///
+ /// Indicates the Connection header.
+ ///
+ Connection,
+ ///
+ /// Indicates the Date header.
+ ///
+ Date,
+ ///
+ /// Indicates the Keep-Alive header.
+ ///
+ KeepAlive,
+ ///
+ /// Indicates the Pragma header.
+ ///
+ Pragma,
+ ///
+ /// Indicates the Trailer header.
+ ///
+ Trailer,
+ ///
+ /// Indicates the Transfer-Encoding header.
+ ///
+ TransferEncoding,
+ ///
+ /// Indicates the Upgrade header.
+ ///
+ Upgrade,
+ ///
+ /// Indicates the Via header.
+ ///
+ Via,
+ ///
+ /// Indicates the Warning header.
+ ///
+ Warning,
+ ///
+ /// Indicates the Allow header.
+ ///
+ Allow,
+ ///
+ /// Indicates the Content-Length header.
+ ///
+ ContentLength,
+ ///
+ /// Indicates the Content-Type header.
+ ///
+ ContentType,
+ ///
+ /// Indicates the Content-Encoding header.
+ ///
+ ContentEncoding,
+ ///
+ /// Indicates the Content-Language header.
+ ///
+ ContentLanguage,
+ ///
+ /// Indicates the Content-Location header.
+ ///
+ ContentLocation,
+ ///
+ /// Indicates the Content-MD5 header.
+ ///
+ ContentMd5,
+ ///
+ /// Indicates the Content-Range header.
+ ///
+ ContentRange,
+ ///
+ /// Indicates the Expires header.
+ ///
+ Expires,
+ ///
+ /// Indicates the Last-Modified header.
+ ///
+ LastModified,
+ ///
+ /// Indicates the Accept header.
+ ///
+ Accept,
+ ///
+ /// Indicates the Accept-Charset header.
+ ///
+ AcceptCharset,
+ ///
+ /// Indicates the Accept-Encoding header.
+ ///
+ AcceptEncoding,
+ ///
+ /// Indicates the Accept-Language header.
+ ///
+ AcceptLanguage,
+ ///
+ /// Indicates the Authorization header.
+ ///
+ Authorization,
+ ///
+ /// Indicates the Cookie header.
+ ///
+ Cookie,
+ ///
+ /// Indicates the Expect header.
+ ///
+ Expect,
+ ///
+ /// Indicates the From header.
+ ///
+ From,
+ ///
+ /// Indicates the Host header.
+ ///
+ Host,
+ ///
+ /// Indicates the If-Match header.
+ ///
+ IfMatch,
+ ///
+ /// Indicates the If-Modified-Since header.
+ ///
+ IfModifiedSince,
+ ///
+ /// Indicates the If-None-Match header.
+ ///
+ IfNoneMatch,
+ ///
+ /// Indicates the If-Range header.
+ ///
+ IfRange,
+ ///
+ /// Indicates the If-Unmodified-Since header.
+ ///
+ IfUnmodifiedSince,
+ ///
+ /// Indicates the Max-Forwards header.
+ ///
+ MaxForwards,
+ ///
+ /// Indicates the Proxy-Authorization header.
+ ///
+ ProxyAuthorization,
+ ///
+ /// Indicates the Referer header.
+ ///
+ Referer,
+ ///
+ /// Indicates the Range header.
+ ///
+ Range,
+ ///
+ /// Indicates the TE header.
+ ///
+ Te,
+ ///
+ /// Indicates the Translate header.
+ ///
+ Translate,
+ ///
+ /// Indicates the User-Agent header.
+ ///
+ UserAgent,
+ ///
+ /// Indicates the Sec-WebSocket-Key header.
+ ///
+ SecWebSocketKey,
+ ///
+ /// Indicates the Sec-WebSocket-Extensions header.
+ ///
+ SecWebSocketExtensions,
+ ///
+ /// Indicates the Sec-WebSocket-Protocol header.
+ ///
+ SecWebSocketProtocol,
+ ///
+ /// Indicates the Sec-WebSocket-Version header.
+ ///
+ SecWebSocketVersion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/HttpRequestHeader.cs.meta b/Assets/External/websocket-sharp/Net/HttpRequestHeader.cs.meta
new file mode 100644
index 00000000..caba7e1d
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpRequestHeader.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: ffdc665e7f1df3b41bf94a12cb43defd
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/HttpResponseHeader.cs b/Assets/External/websocket-sharp/Net/HttpResponseHeader.cs
new file mode 100644
index 00000000..d32afe6c
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpResponseHeader.cs
@@ -0,0 +1,189 @@
+#region License
+/*
+ * HttpResponseHeader.cs
+ *
+ * This code is derived from HttpResponseHeader.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2014-2020 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+namespace WebSocketSharp.Net
+{
+ ///
+ /// Indicates the HTTP header that can be specified in a server response.
+ ///
+ ///
+ /// The headers of this enumeration are defined in
+ /// RFC 2616 or
+ /// RFC 6455.
+ ///
+ public enum HttpResponseHeader
+ {
+ ///
+ /// Indicates the Cache-Control header.
+ ///
+ CacheControl,
+ ///
+ /// Indicates the Connection header.
+ ///
+ Connection,
+ ///
+ /// Indicates the Date header.
+ ///
+ Date,
+ ///
+ /// Indicates the Keep-Alive header.
+ ///
+ KeepAlive,
+ ///
+ /// Indicates the Pragma header.
+ ///
+ Pragma,
+ ///
+ /// Indicates the Trailer header.
+ ///
+ Trailer,
+ ///
+ /// Indicates the Transfer-Encoding header.
+ ///
+ TransferEncoding,
+ ///
+ /// Indicates the Upgrade header.
+ ///
+ Upgrade,
+ ///
+ /// Indicates the Via header.
+ ///
+ Via,
+ ///
+ /// Indicates the Warning header.
+ ///
+ Warning,
+ ///
+ /// Indicates the Allow header.
+ ///
+ Allow,
+ ///
+ /// Indicates the Content-Length header.
+ ///
+ ContentLength,
+ ///
+ /// Indicates the Content-Type header.
+ ///
+ ContentType,
+ ///
+ /// Indicates the Content-Encoding header.
+ ///
+ ContentEncoding,
+ ///
+ /// Indicates the Content-Language header.
+ ///
+ ContentLanguage,
+ ///
+ /// Indicates the Content-Location header.
+ ///
+ ContentLocation,
+ ///
+ /// Indicates the Content-MD5 header.
+ ///
+ ContentMd5,
+ ///
+ /// Indicates the Content-Range header.
+ ///
+ ContentRange,
+ ///
+ /// Indicates the Expires header.
+ ///
+ Expires,
+ ///
+ /// Indicates the Last-Modified header.
+ ///
+ LastModified,
+ ///
+ /// Indicates the Accept-Ranges header.
+ ///
+ AcceptRanges,
+ ///
+ /// Indicates the Age header.
+ ///
+ Age,
+ ///
+ /// Indicates the ETag header.
+ ///
+ ETag,
+ ///
+ /// Indicates the Location header.
+ ///
+ Location,
+ ///
+ /// Indicates the Proxy-Authenticate header.
+ ///
+ ProxyAuthenticate,
+ ///
+ /// Indicates the Retry-After header.
+ ///
+ RetryAfter,
+ ///
+ /// Indicates the Server header.
+ ///
+ Server,
+ ///
+ /// Indicates the Set-Cookie header.
+ ///
+ SetCookie,
+ ///
+ /// Indicates the Vary header.
+ ///
+ Vary,
+ ///
+ /// Indicates the WWW-Authenticate header.
+ ///
+ WwwAuthenticate,
+ ///
+ /// Indicates the Sec-WebSocket-Extensions header.
+ ///
+ SecWebSocketExtensions,
+ ///
+ /// Indicates the Sec-WebSocket-Accept header.
+ ///
+ SecWebSocketAccept,
+ ///
+ /// Indicates the Sec-WebSocket-Protocol header.
+ ///
+ SecWebSocketProtocol,
+ ///
+ /// Indicates the Sec-WebSocket-Version header.
+ ///
+ SecWebSocketVersion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/HttpResponseHeader.cs.meta b/Assets/External/websocket-sharp/Net/HttpResponseHeader.cs.meta
new file mode 100644
index 00000000..d88b3e1f
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpResponseHeader.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 273615a0fa5cbad40806a4188f12a253
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/HttpStatusCode.cs b/Assets/External/websocket-sharp/Net/HttpStatusCode.cs
new file mode 100644
index 00000000..b773a496
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpStatusCode.cs
@@ -0,0 +1,346 @@
+#region License
+/*
+ * HttpStatusCode.cs
+ *
+ * This code is derived from HttpStatusCode.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * It 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
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2001 Ximian, Inc. (http://www.ximian.com)
+ * Copyright (c) 2012-2020 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
+
+namespace WebSocketSharp.Net
+{
+ ///
+ /// Indicates the HTTP status code that can be specified in a server response.
+ ///
+ ///
+ /// The values of this enumeration are defined in
+ /// RFC 2616.
+ ///
+ public enum HttpStatusCode
+ {
+ ///
+ /// Equivalent to status code 100. Indicates that the client should continue
+ /// with its request.
+ ///
+ Continue = 100,
+ ///
+ /// Equivalent to status code 101. Indicates that the server is switching
+ /// the HTTP version or protocol on the connection.
+ ///
+ SwitchingProtocols = 101,
+ ///
+ /// Equivalent to status code 200. Indicates that the client's request has
+ /// succeeded.
+ ///
+ OK = 200,
+ ///
+ /// Equivalent to status code 201. Indicates that the client's request has
+ /// been fulfilled and resulted in a new resource being created.
+ ///
+ Created = 201,
+ ///
+ /// Equivalent to status code 202. Indicates that the client's request has
+ /// been accepted for processing, but the processing has not been completed.
+ ///
+ Accepted = 202,
+ ///
+ /// Equivalent to status code 203. Indicates that the returned metainformation
+ /// is from a local or a third-party copy instead of the origin server.
+ ///
+ NonAuthoritativeInformation = 203,
+ ///
+ /// Equivalent to status code 204. Indicates that the server has fulfilled
+ /// the client's request but does not need to return an entity-body.
+ ///
+ NoContent = 204,
+ ///
+ /// Equivalent to status code 205. Indicates that the server has fulfilled
+ /// the client's request, and the user agent should reset the document view
+ /// which caused the request to be sent.
+ ///
+ ResetContent = 205,
+ ///
+ /// Equivalent to status code 206. Indicates that the server has fulfilled
+ /// the partial GET request for the resource.
+ ///
+ PartialContent = 206,
+ ///
+ ///
+ /// Equivalent to status code 300. Indicates that the requested resource
+ /// corresponds to any of multiple representations.
+ ///
+ ///
+ /// MultipleChoices is a synonym for Ambiguous.
+ ///
+ ///
+ MultipleChoices = 300,
+ ///
+ ///
+ /// Equivalent to status code 300. Indicates that the requested resource
+ /// corresponds to any of multiple representations.
+ ///
+ ///
+ /// Ambiguous is a synonym for MultipleChoices.
+ ///
+ ///
+ Ambiguous = 300,
+ ///
+ ///
+ /// Equivalent to status code 301. Indicates that the requested resource
+ /// has been assigned a new permanent URI and any future references to
+ /// this resource should use one of the returned URIs.
+ ///
+ ///
+ /// MovedPermanently is a synonym for Moved.
+ ///
+ ///
+ MovedPermanently = 301,
+ ///
+ ///
+ /// Equivalent to status code 301. Indicates that the requested resource
+ /// has been assigned a new permanent URI and any future references to
+ /// this resource should use one of the returned URIs.
+ ///
+ ///
+ /// Moved is a synonym for MovedPermanently.
+ ///
+ ///
+ Moved = 301,
+ ///
+ ///
+ /// Equivalent to status code 302. Indicates that the requested resource
+ /// is located temporarily under a different URI.
+ ///
+ ///
+ /// Found is a synonym for Redirect.
+ ///
+ ///
+ Found = 302,
+ ///
+ ///
+ /// Equivalent to status code 302. Indicates that the requested resource
+ /// is located temporarily under a different URI.
+ ///
+ ///
+ /// Redirect is a synonym for Found.
+ ///
+ ///
+ Redirect = 302,
+ ///
+ ///
+ /// Equivalent to status code 303. Indicates that the response to
+ /// the request can be found under a different URI and should be
+ /// retrieved using a GET method on that resource.
+ ///
+ ///
+ /// SeeOther is a synonym for RedirectMethod.
+ ///
+ ///
+ SeeOther = 303,
+ ///
+ ///
+ /// Equivalent to status code 303. Indicates that the response to
+ /// the request can be found under a different URI and should be
+ /// retrieved using a GET method on that resource.
+ ///
+ ///
+ /// RedirectMethod is a synonym for SeeOther.
+ ///
+ ///
+ RedirectMethod = 303,
+ ///
+ /// Equivalent to status code 304. Indicates that the client has performed
+ /// a conditional GET request and access is allowed, but the document has
+ /// not been modified.
+ ///
+ NotModified = 304,
+ ///
+ /// Equivalent to status code 305. Indicates that the requested resource
+ /// must be accessed through the proxy given by the Location field.
+ ///
+ UseProxy = 305,
+ ///
+ /// Equivalent to status code 306. This status code was used in a previous
+ /// version of the specification, is no longer used, and is reserved for
+ /// future use.
+ ///
+ Unused = 306,
+ ///
+ ///
+ /// Equivalent to status code 307. Indicates that the requested resource
+ /// is located temporarily under a different URI.
+ ///
+ ///
+ /// TemporaryRedirect is a synonym for RedirectKeepVerb.
+ ///
+ ///
+ TemporaryRedirect = 307,
+ ///
+ ///
+ /// Equivalent to status code 307. Indicates that the requested resource
+ /// is located temporarily under a different URI.
+ ///
+ ///
+ /// RedirectKeepVerb is a synonym for TemporaryRedirect.
+ ///
+ ///
+ RedirectKeepVerb = 307,
+ ///
+ /// Equivalent to status code 400. Indicates that the client's request could
+ /// not be understood by the server due to malformed syntax.
+ ///
+ BadRequest = 400,
+ ///
+ /// Equivalent to status code 401. Indicates that the client's request
+ /// requires user authentication.
+ ///
+ Unauthorized = 401,
+ ///
+ /// Equivalent to status code 402. This status code is reserved for future
+ /// use.
+ ///
+ PaymentRequired = 402,
+ ///
+ /// Equivalent to status code 403. Indicates that the server understood
+ /// the client's request but is refusing to fulfill it.
+ ///
+ Forbidden = 403,
+ ///
+ /// Equivalent to status code 404. Indicates that the server has not found
+ /// anything matching the request URI.
+ ///
+ NotFound = 404,
+ ///
+ /// Equivalent to status code 405. Indicates that the method specified
+ /// in the request line is not allowed for the resource identified by
+ /// the request URI.
+ ///
+ MethodNotAllowed = 405,
+ ///
+ /// Equivalent to status code 406. Indicates that the server does not
+ /// have the appropriate resource to respond to the Accept headers in
+ /// the client's request.
+ ///
+ NotAcceptable = 406,
+ ///
+ /// Equivalent to status code 407. Indicates that the client must first
+ /// authenticate itself with the proxy.
+ ///
+ ProxyAuthenticationRequired = 407,
+ ///
+ /// Equivalent to status code 408. Indicates that the client did not produce
+ /// a request within the time that the server was prepared to wait.
+ ///
+ RequestTimeout = 408,
+ ///
+ /// Equivalent to status code 409. Indicates that the client's request could
+ /// not be completed due to a conflict on the server.
+ ///
+ Conflict = 409,
+ ///
+ /// Equivalent to status code 410. Indicates that the requested resource is
+ /// no longer available at the server and no forwarding address is known.
+ ///
+ Gone = 410,
+ ///
+ /// Equivalent to status code 411. Indicates that the server refuses to
+ /// accept the client's request without a defined Content-Length.
+ ///
+ LengthRequired = 411,
+ ///
+ /// Equivalent to status code 412. Indicates that the precondition given in
+ /// one or more of the request headers evaluated to false when it was tested
+ /// on the server.
+ ///
+ PreconditionFailed = 412,
+ ///
+ /// Equivalent to status code 413. Indicates that the entity of the client's
+ /// request is larger than the server is willing or able to process.
+ ///
+ RequestEntityTooLarge = 413,
+ ///
+ /// Equivalent to status code 414. Indicates that the request URI is longer
+ /// than the server is willing to interpret.
+ ///
+ RequestUriTooLong = 414,
+ ///
+ /// Equivalent to status code 415. Indicates that the entity of the client's
+ /// request is in a format not supported by the requested resource for the
+ /// requested method.
+ ///
+ UnsupportedMediaType = 415,
+ ///
+ /// Equivalent to status code 416. Indicates that none of the range
+ /// specifier values in a Range request header overlap the current
+ /// extent of the selected resource.
+ ///
+ RequestedRangeNotSatisfiable = 416,
+ ///
+ /// Equivalent to status code 417. Indicates that the expectation given in
+ /// an Expect request header could not be met by the server.
+ ///
+ ExpectationFailed = 417,
+ ///
+ /// Equivalent to status code 500. Indicates that the server encountered
+ /// an unexpected condition which prevented it from fulfilling the client's
+ /// request.
+ ///
+ InternalServerError = 500,
+ ///
+ /// Equivalent to status code 501. Indicates that the server does not
+ /// support the functionality required to fulfill the client's request.
+ ///
+ NotImplemented = 501,
+ ///
+ /// Equivalent to status code 502. Indicates that a gateway or proxy server
+ /// received an invalid response from the upstream server.
+ ///
+ BadGateway = 502,
+ ///
+ /// Equivalent to status code 503. Indicates that the server is currently
+ /// unable to handle the client's request due to a temporary overloading
+ /// or maintenance of the server.
+ ///
+ ServiceUnavailable = 503,
+ ///
+ /// Equivalent to status code 504. Indicates that a gateway or proxy server
+ /// did not receive a timely response from the upstream server or some other
+ /// auxiliary server.
+ ///
+ GatewayTimeout = 504,
+ ///
+ /// Equivalent to status code 505. Indicates that the server does not
+ /// support the HTTP version used in the client's request.
+ ///
+ HttpVersionNotSupported = 505,
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/HttpStatusCode.cs.meta b/Assets/External/websocket-sharp/Net/HttpStatusCode.cs.meta
new file mode 100644
index 00000000..164b0d0d
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpStatusCode.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: b0ab739b61e8c01498c39a4ab5289949
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/HttpStreamAsyncResult.cs b/Assets/External/websocket-sharp/Net/HttpStreamAsyncResult.cs
new file mode 100644
index 00000000..09447ea2
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpStreamAsyncResult.cs
@@ -0,0 +1,201 @@
+#region License
+/*
+ * HttpStreamAsyncResult.cs
+ *
+ * This code is derived from HttpStreamAsyncResult.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2012-2021 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+using System;
+using System.Threading;
+
+namespace WebSocketSharp.Net
+{
+ internal class HttpStreamAsyncResult : IAsyncResult
+ {
+ #region Private Fields
+
+ private byte[] _buffer;
+ private AsyncCallback _callback;
+ private bool _completed;
+ private int _count;
+ private Exception _exception;
+ private int _offset;
+ private object _state;
+ private object _sync;
+ private int _syncRead;
+ private ManualResetEvent _waitHandle;
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal HttpStreamAsyncResult (AsyncCallback callback, object state)
+ {
+ _callback = callback;
+ _state = state;
+
+ _sync = new object ();
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal byte[] Buffer {
+ get {
+ return _buffer;
+ }
+
+ set {
+ _buffer = value;
+ }
+ }
+
+ internal int Count {
+ get {
+ return _count;
+ }
+
+ set {
+ _count = value;
+ }
+ }
+
+ internal Exception Exception {
+ get {
+ return _exception;
+ }
+ }
+
+ internal bool HasException {
+ get {
+ return _exception != null;
+ }
+ }
+
+ internal int Offset {
+ get {
+ return _offset;
+ }
+
+ set {
+ _offset = value;
+ }
+ }
+
+ internal int SyncRead {
+ get {
+ return _syncRead;
+ }
+
+ set {
+ _syncRead = value;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ public object AsyncState {
+ get {
+ return _state;
+ }
+ }
+
+ public WaitHandle AsyncWaitHandle {
+ get {
+ lock (_sync) {
+ if (_waitHandle == null)
+ _waitHandle = new ManualResetEvent (_completed);
+
+ return _waitHandle;
+ }
+ }
+ }
+
+ public bool CompletedSynchronously {
+ get {
+ return _syncRead == _count;
+ }
+ }
+
+ public bool IsCompleted {
+ get {
+ lock (_sync)
+ return _completed;
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal void Complete ()
+ {
+ lock (_sync) {
+ if (_completed)
+ return;
+
+ _completed = true;
+
+ if (_waitHandle != null)
+ _waitHandle.Set ();
+
+ if (_callback != null)
+ _callback.BeginInvoke (this, ar => _callback.EndInvoke (ar), null);
+ }
+ }
+
+ internal void Complete (Exception exception)
+ {
+ lock (_sync) {
+ if (_completed)
+ return;
+
+ _completed = true;
+ _exception = exception;
+
+ if (_waitHandle != null)
+ _waitHandle.Set ();
+
+ if (_callback != null)
+ _callback.BeginInvoke (this, ar => _callback.EndInvoke (ar), null);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/HttpStreamAsyncResult.cs.meta b/Assets/External/websocket-sharp/Net/HttpStreamAsyncResult.cs.meta
new file mode 100644
index 00000000..519974e4
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpStreamAsyncResult.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 7b214852a23637e46bd25b72af883f25
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/HttpUtility.cs b/Assets/External/websocket-sharp/Net/HttpUtility.cs
new file mode 100644
index 00000000..b7db7b92
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpUtility.cs
@@ -0,0 +1,1237 @@
+#region License
+/*
+ * HttpUtility.cs
+ *
+ * This code is derived from HttpUtility.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005-2009 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2012-2024 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
+
+#region Authors
+/*
+ * Authors:
+ * - Patrik Torstensson
+ * - Wictor Wilén (decode/encode functions)
+ * - Tim Coleman
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Globalization;
+using System.IO;
+using System.Security.Principal;
+using System.Text;
+
+namespace WebSocketSharp.Net
+{
+ internal static class HttpUtility
+ {
+ #region Private Fields
+
+ private static Dictionary _entities;
+ private static char[] _hexChars;
+ private static object _sync;
+
+ #endregion
+
+ #region Static Constructor
+
+ static HttpUtility ()
+ {
+ _hexChars = "0123456789ABCDEF".ToCharArray ();
+ _sync = new object ();
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private static Dictionary getEntities ()
+ {
+ lock (_sync) {
+ if (_entities == null)
+ initEntities ();
+
+ return _entities;
+ }
+ }
+
+ private static int getNumber (char c)
+ {
+ 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;
+ }
+
+ private static int getNumber (byte[] bytes, int offset, int count)
+ {
+ var ret = 0;
+
+ var end = offset + count - 1;
+
+ for (var i = offset; i <= end; i++) {
+ var c = (char) bytes[i];
+ var n = getNumber (c);
+
+ if (n == -1)
+ return -1;
+
+ ret = (ret << 4) + n;
+ }
+
+ return ret;
+ }
+
+ private static int getNumber (string s, int offset, int count)
+ {
+ var ret = 0;
+
+ var end = offset + count - 1;
+
+ for (var i = offset; i <= end; i++) {
+ var c = s[i];
+ var n = getNumber (c);
+
+ if (n == -1)
+ return -1;
+
+ ret = (ret << 4) + n;
+ }
+
+ return ret;
+ }
+
+ private static string htmlDecode (string s)
+ {
+ var buff = new StringBuilder ();
+
+ // 0: None
+ // 1: Right after '&'
+ // 2: Between '&' and ';' but no NCR
+ // 3: '#' found after '&' and getting numbers
+ // 4: 'x' found after '#' and getting numbers
+ var state = 0;
+
+ var reference = new StringBuilder ();
+ var num = 0;
+
+ foreach (var c in s) {
+ if (state == 0) {
+ if (c == '&') {
+ reference.Append ('&');
+
+ state = 1;
+
+ continue;
+ }
+
+ buff.Append (c);
+
+ continue;
+ }
+
+ if (c == '&') {
+ buff.Append (reference.ToString ());
+
+ reference.Length = 0;
+
+ reference.Append ('&');
+
+ state = 1;
+
+ continue;
+ }
+
+ reference.Append (c);
+
+ if (state == 1) {
+ if (c == ';') {
+ buff.Append (reference.ToString ());
+
+ reference.Length = 0;
+ state = 0;
+
+ continue;
+ }
+
+ num = 0;
+ state = c == '#' ? 3 : 2;
+
+ continue;
+ }
+
+ if (state == 2) {
+ if (c == ';') {
+ var entity = reference.ToString ();
+ var name = entity.Substring (1, entity.Length - 2);
+
+ var entities = getEntities ();
+
+ if (entities.ContainsKey (name))
+ buff.Append (entities[name]);
+ else
+ buff.Append (entity);
+
+ reference.Length = 0;
+ state = 0;
+
+ continue;
+ }
+
+ continue;
+ }
+
+ if (state == 3) {
+ if (c == ';') {
+ if (reference.Length > 3 && num < 65536)
+ buff.Append ((char) num);
+ else
+ buff.Append (reference.ToString ());
+
+ reference.Length = 0;
+ state = 0;
+
+ continue;
+ }
+
+ if (c == 'x') {
+ state = reference.Length == 3 ? 4 : 2;
+
+ continue;
+ }
+
+ if (!isNumeric (c)) {
+ state = 2;
+
+ continue;
+ }
+
+ num = num * 10 + (c - '0');
+
+ continue;
+ }
+
+ if (state == 4) {
+ if (c == ';') {
+ if (reference.Length > 4 && num < 65536)
+ buff.Append ((char) num);
+ else
+ buff.Append (reference.ToString ());
+
+ reference.Length = 0;
+ state = 0;
+
+ continue;
+ }
+
+ var n = getNumber (c);
+
+ if (n == -1) {
+ state = 2;
+
+ continue;
+ }
+
+ num = (num << 4) + n;
+ }
+ }
+
+ if (reference.Length > 0)
+ buff.Append (reference.ToString ());
+
+ return buff.ToString ();
+ }
+
+ ///
+ /// Converts the specified string to an HTML-encoded string.
+ ///
+ ///
+ ///
+ /// This method starts encoding with a NCR from the character code 160
+ /// but does not stop at the character code 255.
+ ///
+ ///
+ /// One reason is the unicode characters < and > that
+ /// look like < and >.
+ ///
+ ///
+ ///
+ /// A that represents an encoded string.
+ ///
+ ///
+ /// A to encode.
+ ///
+ ///
+ /// A : true if encodes without a NCR;
+ /// otherwise, false.
+ ///
+ private static string htmlEncode (string s, bool minimal)
+ {
+ var buff = new StringBuilder ();
+
+ foreach (var c in s) {
+ if (c == '"') {
+ buff.Append (""");
+
+ continue;
+ }
+
+ if (c == '&') {
+ buff.Append ("&");
+
+ continue;
+ }
+
+ if (c == '<') {
+ buff.Append ("<");
+
+ continue;
+ }
+
+ if (c == '>') {
+ buff.Append (">");
+
+ continue;
+ }
+
+ if (c > 159) {
+ if (!minimal) {
+ var val = String.Format ("{0};", (int) c);
+
+ buff.Append (val);
+
+ continue;
+ }
+ }
+
+ buff.Append (c);
+ }
+
+ return buff.ToString ();
+ }
+
+ ///
+ /// Initializes the _entities field.
+ ///
+ ///
+ /// This method builds a dictionary of HTML character entity references.
+ /// This dictionary comes from the HTML 4.01 W3C recommendation.
+ ///
+ private static void initEntities ()
+ {
+ _entities = new Dictionary ();
+
+ _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');
+ }
+
+ private static bool isAlphabet (char c)
+ {
+ return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
+ }
+
+ private static bool isNumeric (char c)
+ {
+ return c >= '0' && c <= '9';
+ }
+
+ private static bool isUnreserved (char c)
+ {
+ return c == '*'
+ || c == '-'
+ || c == '.'
+ || c == '_';
+ }
+
+ private static bool isUnreservedInRfc2396 (char c)
+ {
+ return c == '!'
+ || c == '\''
+ || c == '('
+ || c == ')'
+ || c == '*'
+ || c == '-'
+ || c == '.'
+ || c == '_'
+ || c == '~';
+ }
+
+ private static bool isUnreservedInRfc3986 (char c)
+ {
+ return c == '-'
+ || c == '.'
+ || c == '_'
+ || c == '~';
+ }
+
+ private static byte[] urlDecodeToBytes (byte[] bytes, int offset, int count)
+ {
+ using (var buff = new MemoryStream ()) {
+ var end = offset + count - 1;
+
+ for (var i = offset; i <= end; i++) {
+ var b = bytes[i];
+ var c = (char) b;
+
+ if (c == '%') {
+ if (i > end - 2)
+ break;
+
+ var num = getNumber (bytes, i + 1, 2);
+
+ if (num == -1)
+ break;
+
+ buff.WriteByte ((byte) num);
+
+ i += 2;
+
+ continue;
+ }
+
+ if (c == '+') {
+ buff.WriteByte ((byte) ' ');
+
+ continue;
+ }
+
+ buff.WriteByte (b);
+ }
+
+ buff.Close ();
+
+ return buff.ToArray ();
+ }
+ }
+
+ private static void urlEncode (byte b, Stream output)
+ {
+ if (b > 31 && b < 127) {
+ var c = (char) b;
+
+ if (c == ' ') {
+ output.WriteByte ((byte) '+');
+
+ return;
+ }
+
+ if (isNumeric (c)) {
+ output.WriteByte (b);
+
+ return;
+ }
+
+ if (isAlphabet (c)) {
+ output.WriteByte (b);
+
+ return;
+ }
+
+ if (isUnreserved (c)) {
+ output.WriteByte (b);
+
+ return;
+ }
+ }
+
+ var i = (int) b;
+ var bytes = new byte[] {
+ (byte) '%',
+ (byte) _hexChars[i >> 4],
+ (byte) _hexChars[i & 0x0F]
+ };
+
+ output.Write (bytes, 0, 3);
+ }
+
+ private static byte[] urlEncodeToBytes (byte[] bytes, int offset, int count)
+ {
+ using (var buff = new MemoryStream ()) {
+ var end = offset + count - 1;
+
+ for (var i = offset; i <= end; i++)
+ urlEncode (bytes[i], buff);
+
+ buff.Close ();
+
+ return buff.ToArray ();
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal static Uri CreateRequestUrl (
+ string requestUri,
+ string host,
+ bool websocketRequest,
+ bool secure
+ )
+ {
+ if (requestUri == null || requestUri.Length == 0)
+ return null;
+
+ if (host == null || host.Length == 0)
+ return null;
+
+ string schm = null;
+ string path = null;
+
+ if (requestUri.IndexOf ('/') == 0) {
+ path = requestUri;
+ }
+ else if (requestUri.MaybeUri ()) {
+ Uri uri;
+
+ if (!Uri.TryCreate (requestUri, UriKind.Absolute, out uri))
+ return null;
+
+ schm = uri.Scheme;
+ var valid = websocketRequest
+ ? schm == "ws" || schm == "wss"
+ : schm == "http" || schm == "https";
+
+ if (!valid)
+ return null;
+
+ host = uri.Authority;
+ path = uri.PathAndQuery;
+ }
+ else if (requestUri == "*") {
+ }
+ else {
+ // As the authority form.
+
+ host = requestUri;
+ }
+
+ if (schm == null) {
+ schm = websocketRequest
+ ? (secure ? "wss" : "ws")
+ : (secure ? "https" : "http");
+ }
+
+ if (host.IndexOf (':') == -1)
+ host = String.Format ("{0}:{1}", host, secure ? 443 : 80);
+
+ var url = String.Format ("{0}://{1}{2}", schm, host, path);
+ Uri ret;
+
+ return Uri.TryCreate (url, UriKind.Absolute, out ret) ? ret : null;
+ }
+
+ internal static IPrincipal CreateUser (
+ string response,
+ AuthenticationSchemes scheme,
+ string realm,
+ string method,
+ Func credentialsFinder
+ )
+ {
+ if (response == null || response.Length == 0)
+ return null;
+
+ if (scheme == AuthenticationSchemes.Digest) {
+ if (realm == null || realm.Length == 0)
+ return null;
+
+ if (method == null || method.Length == 0)
+ return null;
+ }
+ else {
+ if (scheme != AuthenticationSchemes.Basic)
+ return null;
+ }
+
+ if (credentialsFinder == null)
+ return null;
+
+ var compType = StringComparison.OrdinalIgnoreCase;
+
+ if (!response.StartsWith (scheme.ToString (), compType))
+ return null;
+
+ var res = AuthenticationResponse.Parse (response);
+
+ if (res == null)
+ return null;
+
+ var id = res.ToIdentity ();
+
+ if (id == null)
+ return null;
+
+ NetworkCredential cred = null;
+
+ try {
+ cred = credentialsFinder (id);
+ }
+ catch {
+ }
+
+ if (cred == null)
+ return null;
+
+ if (scheme == AuthenticationSchemes.Basic) {
+ var basicId = (HttpBasicIdentity) id;
+
+ return basicId.Password == cred.Password
+ ? new GenericPrincipal (id, cred.Roles)
+ : null;
+ }
+
+ var digestId = (HttpDigestIdentity) id;
+
+ return digestId.IsValid (cred.Password, realm, method, null)
+ ? new GenericPrincipal (id, cred.Roles)
+ : null;
+ }
+
+ internal static Encoding GetEncoding (string contentType)
+ {
+ var name = "charset=";
+ var compType = StringComparison.OrdinalIgnoreCase;
+
+ foreach (var elm in contentType.SplitHeaderValue (';')) {
+ var part = elm.Trim ();
+
+ if (!part.StartsWith (name, compType))
+ continue;
+
+ var val = part.GetValue ('=', true);
+
+ if (val == null || val.Length == 0)
+ return null;
+
+ return Encoding.GetEncoding (val);
+ }
+
+ return null;
+ }
+
+ internal static bool TryGetEncoding (
+ string contentType,
+ out Encoding result
+ )
+ {
+ result = null;
+
+ try {
+ result = GetEncoding (contentType);
+ }
+ catch {
+ return false;
+ }
+
+ return result != null;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public static string HtmlAttributeEncode (string s)
+ {
+ if (s == null)
+ throw new ArgumentNullException ("s");
+
+ return s.Length > 0 ? htmlEncode (s, true) : s;
+ }
+
+ public static void HtmlAttributeEncode (string s, TextWriter output)
+ {
+ if (s == null)
+ throw new ArgumentNullException ("s");
+
+ if (output == null)
+ throw new ArgumentNullException ("output");
+
+ if (s.Length == 0)
+ return;
+
+ var encodedS = htmlEncode (s, true);
+
+ output.Write (encodedS);
+ }
+
+ public static string HtmlDecode (string s)
+ {
+ if (s == null)
+ throw new ArgumentNullException ("s");
+
+ return s.Length > 0 ? htmlDecode (s) : s;
+ }
+
+ public static void HtmlDecode (string s, TextWriter output)
+ {
+ if (s == null)
+ throw new ArgumentNullException ("s");
+
+ if (output == null)
+ throw new ArgumentNullException ("output");
+
+ if (s.Length == 0)
+ return;
+
+ var decodedS = htmlDecode (s);
+
+ output.Write (decodedS);
+ }
+
+ public static string HtmlEncode (string s)
+ {
+ if (s == null)
+ throw new ArgumentNullException ("s");
+
+ return s.Length > 0 ? htmlEncode (s, false) : s;
+ }
+
+ public static void HtmlEncode (string s, TextWriter output)
+ {
+ if (s == null)
+ throw new ArgumentNullException ("s");
+
+ if (output == null)
+ throw new ArgumentNullException ("output");
+
+ if (s.Length == 0)
+ return;
+
+ var encodedS = htmlEncode (s, false);
+
+ output.Write (encodedS);
+ }
+
+ public static string UrlDecode (string s)
+ {
+ return UrlDecode (s, Encoding.UTF8);
+ }
+
+ public static string UrlDecode (byte[] bytes, Encoding encoding)
+ {
+ if (bytes == null)
+ throw new ArgumentNullException ("bytes");
+
+ var len = bytes.Length;
+
+ if (len == 0)
+ return String.Empty;
+
+ var decodedBytes = urlDecodeToBytes (bytes, 0, len);
+
+ return (encoding ?? Encoding.UTF8).GetString (decodedBytes);
+ }
+
+ public static string UrlDecode (string s, Encoding encoding)
+ {
+ if (s == null)
+ throw new ArgumentNullException ("s");
+
+ if (s.Length == 0)
+ return s;
+
+ var bytes = Encoding.ASCII.GetBytes (s);
+ var decodedBytes = urlDecodeToBytes (bytes, 0, bytes.Length);
+
+ return (encoding ?? Encoding.UTF8).GetString (decodedBytes);
+ }
+
+ public static string UrlDecode (
+ byte[] bytes,
+ int offset,
+ int count,
+ Encoding encoding
+ )
+ {
+ if (bytes == null)
+ throw new ArgumentNullException ("bytes");
+
+ var len = bytes.Length;
+
+ if (len == 0) {
+ if (offset != 0)
+ throw new ArgumentOutOfRangeException ("offset");
+
+ if (count != 0)
+ throw new ArgumentOutOfRangeException ("count");
+
+ return String.Empty;
+ }
+
+ if (offset < 0 || offset >= len)
+ throw new ArgumentOutOfRangeException ("offset");
+
+ if (count < 0 || count > len - offset)
+ throw new ArgumentOutOfRangeException ("count");
+
+ if (count == 0)
+ return String.Empty;
+
+ var decodedBytes = urlDecodeToBytes (bytes, offset, count);
+
+ return (encoding ?? Encoding.UTF8).GetString (decodedBytes);
+ }
+
+ public static byte[] UrlDecodeToBytes (byte[] bytes)
+ {
+ if (bytes == null)
+ throw new ArgumentNullException ("bytes");
+
+ var len = bytes.Length;
+
+ return len > 0 ? urlDecodeToBytes (bytes, 0, len) : bytes;
+ }
+
+ public static byte[] UrlDecodeToBytes (string s)
+ {
+ if (s == null)
+ throw new ArgumentNullException ("s");
+
+ if (s.Length == 0)
+ return new byte[0];
+
+ var bytes = Encoding.ASCII.GetBytes (s);
+
+ return urlDecodeToBytes (bytes, 0, bytes.Length);
+ }
+
+ public static byte[] UrlDecodeToBytes (byte[] bytes, int offset, int count)
+ {
+ if (bytes == null)
+ throw new ArgumentNullException ("bytes");
+
+ var len = bytes.Length;
+
+ if (len == 0) {
+ if (offset != 0)
+ throw new ArgumentOutOfRangeException ("offset");
+
+ if (count != 0)
+ throw new ArgumentOutOfRangeException ("count");
+
+ return bytes;
+ }
+
+ if (offset < 0 || offset >= len)
+ throw new ArgumentOutOfRangeException ("offset");
+
+ if (count < 0 || count > len - offset)
+ throw new ArgumentOutOfRangeException ("count");
+
+ return count > 0 ? urlDecodeToBytes (bytes, offset, count) : new byte[0];
+ }
+
+ public static string UrlEncode (byte[] bytes)
+ {
+ if (bytes == null)
+ throw new ArgumentNullException ("bytes");
+
+ var len = bytes.Length;
+
+ if (len == 0)
+ return String.Empty;
+
+ var encodedBytes = urlEncodeToBytes (bytes, 0, len);
+
+ return Encoding.ASCII.GetString (encodedBytes);
+ }
+
+ public static string UrlEncode (string s)
+ {
+ return UrlEncode (s, Encoding.UTF8);
+ }
+
+ public static string UrlEncode (string s, Encoding encoding)
+ {
+ if (s == null)
+ throw new ArgumentNullException ("s");
+
+ var len = s.Length;
+
+ if (len == 0)
+ return s;
+
+ if (encoding == null)
+ encoding = Encoding.UTF8;
+
+ var maxCnt = encoding.GetMaxByteCount (len);
+ var bytes = new byte[maxCnt];
+ var cnt = encoding.GetBytes (s, 0, len, bytes, 0);
+ var encodedBytes = urlEncodeToBytes (bytes, 0, cnt);
+
+ return Encoding.ASCII.GetString (encodedBytes);
+ }
+
+ public static string UrlEncode (byte[] bytes, int offset, int count)
+ {
+ if (bytes == null)
+ throw new ArgumentNullException ("bytes");
+
+ var len = bytes.Length;
+
+ if (len == 0) {
+ if (offset != 0)
+ throw new ArgumentOutOfRangeException ("offset");
+
+ if (count != 0)
+ throw new ArgumentOutOfRangeException ("count");
+
+ return String.Empty;
+ }
+
+ if (offset < 0 || offset >= len)
+ throw new ArgumentOutOfRangeException ("offset");
+
+ if (count < 0 || count > len - offset)
+ throw new ArgumentOutOfRangeException ("count");
+
+ if (count == 0)
+ return String.Empty;
+
+ var encodedBytes = urlEncodeToBytes (bytes, offset, count);
+
+ return Encoding.ASCII.GetString (encodedBytes);
+ }
+
+ public static byte[] UrlEncodeToBytes (byte[] bytes)
+ {
+ if (bytes == null)
+ throw new ArgumentNullException ("bytes");
+
+ var len = bytes.Length;
+
+ return len > 0 ? urlEncodeToBytes (bytes, 0, len) : bytes;
+ }
+
+ public static byte[] UrlEncodeToBytes (string s)
+ {
+ return UrlEncodeToBytes (s, Encoding.UTF8);
+ }
+
+ public static byte[] UrlEncodeToBytes (string s, Encoding encoding)
+ {
+ if (s == null)
+ throw new ArgumentNullException ("s");
+
+ if (s.Length == 0)
+ return new byte[0];
+
+ var bytes = (encoding ?? Encoding.UTF8).GetBytes (s);
+
+ return urlEncodeToBytes (bytes, 0, bytes.Length);
+ }
+
+ public static byte[] UrlEncodeToBytes (byte[] bytes, int offset, int count)
+ {
+ if (bytes == null)
+ throw new ArgumentNullException ("bytes");
+
+ var len = bytes.Length;
+
+ if (len == 0) {
+ if (offset != 0)
+ throw new ArgumentOutOfRangeException ("offset");
+
+ if (count != 0)
+ throw new ArgumentOutOfRangeException ("count");
+
+ return bytes;
+ }
+
+ if (offset < 0 || offset >= len)
+ throw new ArgumentOutOfRangeException ("offset");
+
+ if (count < 0 || count > len - offset)
+ throw new ArgumentOutOfRangeException ("count");
+
+ return count > 0 ? urlEncodeToBytes (bytes, offset, count) : new byte[0];
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/HttpUtility.cs.meta b/Assets/External/websocket-sharp/Net/HttpUtility.cs.meta
new file mode 100644
index 00000000..c690fa0b
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpUtility.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: f570a1781ffb6184ea93696443806d33
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/HttpVersion.cs b/Assets/External/websocket-sharp/Net/HttpVersion.cs
new file mode 100644
index 00000000..95f8f0a3
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpVersion.cs
@@ -0,0 +1,73 @@
+#region License
+/*
+ * HttpVersion.cs
+ *
+ * This code is derived from HttpVersion.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2024 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
+
+#region Authors
+/*
+ * Authors:
+ * - Lawrence Pit
+ */
+#endregion
+
+using System;
+
+namespace WebSocketSharp.Net
+{
+ ///
+ /// Provides the HTTP version numbers.
+ ///
+ public class HttpVersion
+ {
+ #region Public Fields
+
+ ///
+ /// Provides a instance for the HTTP/1.0.
+ ///
+ public static readonly Version Version10 = new Version (1, 0);
+
+ ///
+ /// Provides a instance for the HTTP/1.1.
+ ///
+ public static readonly Version Version11 = new Version (1, 1);
+
+ #endregion
+
+ #region Public Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public HttpVersion ()
+ {
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/HttpVersion.cs.meta b/Assets/External/websocket-sharp/Net/HttpVersion.cs.meta
new file mode 100644
index 00000000..b45dcb23
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/HttpVersion.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 5b7b5bd46350c3642905967564402d63
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/InputChunkState.cs b/Assets/External/websocket-sharp/Net/InputChunkState.cs
new file mode 100644
index 00000000..f50ad6b7
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/InputChunkState.cs
@@ -0,0 +1,52 @@
+#region License
+/*
+ * InputChunkState.cs
+ *
+ * This code is derived from ChunkStream.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2003 Ximian, Inc (http://www.ximian.com)
+ * Copyright (c) 2014-2015 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+using System;
+
+namespace WebSocketSharp.Net
+{
+ internal enum InputChunkState
+ {
+ None,
+ Data,
+ DataEnded,
+ Trailer,
+ End
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/InputChunkState.cs.meta b/Assets/External/websocket-sharp/Net/InputChunkState.cs.meta
new file mode 100644
index 00000000..0bda6fbe
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/InputChunkState.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 823ae53558ee9e746a94dbdb388f4c17
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/InputState.cs b/Assets/External/websocket-sharp/Net/InputState.cs
new file mode 100644
index 00000000..9f566d24
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/InputState.cs
@@ -0,0 +1,49 @@
+#region License
+/*
+ * InputState.cs
+ *
+ * This code is derived from HttpConnection.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2014-2015 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+using System;
+
+namespace WebSocketSharp.Net
+{
+ internal enum InputState
+ {
+ RequestLine,
+ Headers
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/InputState.cs.meta b/Assets/External/websocket-sharp/Net/InputState.cs.meta
new file mode 100644
index 00000000..0da85971
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/InputState.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: c4bf22ee9c7cd2a44b73f930437c3a36
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/LineState.cs b/Assets/External/websocket-sharp/Net/LineState.cs
new file mode 100644
index 00000000..84e271a7
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/LineState.cs
@@ -0,0 +1,50 @@
+#region License
+/*
+ * LineState.cs
+ *
+ * This code is derived from HttpConnection.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2014-2015 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+using System;
+
+namespace WebSocketSharp.Net
+{
+ internal enum LineState
+ {
+ None,
+ Cr,
+ Lf
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/LineState.cs.meta b/Assets/External/websocket-sharp/Net/LineState.cs.meta
new file mode 100644
index 00000000..3d8c5098
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/LineState.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 343792b09aa8edc4294cd342299555b9
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/NetworkCredential.cs b/Assets/External/websocket-sharp/Net/NetworkCredential.cs
new file mode 100644
index 00000000..f97df971
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/NetworkCredential.cs
@@ -0,0 +1,217 @@
+#region License
+/*
+ * NetworkCredential.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2014-2024 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;
+
+namespace WebSocketSharp.Net
+{
+ ///
+ /// Provides the credentials for the password-based authentication.
+ ///
+ public class NetworkCredential
+ {
+ #region Private Fields
+
+ private string _domain;
+ private static readonly string[] _noRoles;
+ private string _password;
+ private string[] _roles;
+ private string _username;
+
+ #endregion
+
+ #region Static Constructor
+
+ static NetworkCredential ()
+ {
+ _noRoles = new string[0];
+ }
+
+ #endregion
+
+ #region Public Constructors
+
+ ///
+ /// Initializes a new instance of the class
+ /// with the specified username and password.
+ ///
+ ///
+ /// A that specifies the username associated with
+ /// the credentials.
+ ///
+ ///
+ /// A that specifies the password for the username
+ /// associated with the credentials.
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// is .
+ ///
+ public NetworkCredential (string username, string password)
+ : this (username, password, null, null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// with the specified username, password, domain and roles.
+ ///
+ ///
+ /// A that specifies the username associated with
+ /// the credentials.
+ ///
+ ///
+ /// A that specifies the password for the username
+ /// associated with the credentials.
+ ///
+ ///
+ /// A that specifies the domain associated with
+ /// the credentials.
+ ///
+ ///
+ /// An array of that specifies the roles associated
+ /// with the credentials if any.
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// is .
+ ///
+ public NetworkCredential (
+ string username,
+ string password,
+ string domain,
+ params string[] roles
+ )
+ {
+ if (username == null)
+ throw new ArgumentNullException ("username");
+
+ if (username.Length == 0)
+ throw new ArgumentException ("An empty string.", "username");
+
+ _username = username;
+ _password = password;
+ _domain = domain;
+ _roles = roles;
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the domain associated with the credentials.
+ ///
+ ///
+ ///
+ /// A that represents the domain name
+ /// to which the username belongs.
+ ///
+ ///
+ /// An empty string if the value was initialized with
+ /// .
+ ///
+ ///
+ public string Domain {
+ get {
+ return _domain ?? String.Empty;
+ }
+
+ internal set {
+ _domain = value;
+ }
+ }
+
+ ///
+ /// Gets the password for the username associated with the credentials.
+ ///
+ ///
+ ///
+ /// A that represents the password.
+ ///
+ ///
+ /// An empty string if the value was initialized with
+ /// .
+ ///
+ ///
+ public string Password {
+ get {
+ return _password ?? String.Empty;
+ }
+
+ internal set {
+ _password = value;
+ }
+ }
+
+ ///
+ /// Gets the roles associated with the credentials.
+ ///
+ ///
+ ///
+ /// An array of that represents the role names
+ /// to which the username belongs.
+ ///
+ ///
+ /// An empty array if the value was initialized with
+ /// .
+ ///
+ ///
+ public string[] Roles {
+ get {
+ return _roles ?? _noRoles;
+ }
+
+ internal set {
+ _roles = value;
+ }
+ }
+
+ ///
+ /// Gets the username associated with the credentials.
+ ///
+ ///
+ /// A that represents the username.
+ ///
+ public string Username {
+ get {
+ return _username;
+ }
+
+ internal set {
+ _username = value;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/NetworkCredential.cs.meta b/Assets/External/websocket-sharp/Net/NetworkCredential.cs.meta
new file mode 100644
index 00000000..a9fd59a5
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/NetworkCredential.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: ae1432d0841e559499a32e91db01a3e6
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/QueryStringCollection.cs b/Assets/External/websocket-sharp/Net/QueryStringCollection.cs
new file mode 100644
index 00000000..d5b7a8d9
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/QueryStringCollection.cs
@@ -0,0 +1,144 @@
+#region License
+/*
+ * QueryStringCollection.cs
+ *
+ * This code is derived from HttpUtility.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005-2009 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2018-2023 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
+
+#region Authors
+/*
+ * Authors:
+ * - Patrik Torstensson
+ * - Wictor Wilén (decode/encode functions)
+ * - Tim Coleman
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+using System;
+using System.Collections.Specialized;
+using System.Text;
+
+namespace WebSocketSharp.Net
+{
+ internal sealed class QueryStringCollection : NameValueCollection
+ {
+ #region Public Constructors
+
+ public QueryStringCollection ()
+ {
+ }
+
+ public QueryStringCollection (int capacity)
+ : base (capacity)
+ {
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public static QueryStringCollection Parse (string query)
+ {
+ return Parse (query, Encoding.UTF8);
+ }
+
+ public static QueryStringCollection Parse (string query, Encoding encoding)
+ {
+ if (query == null)
+ return new QueryStringCollection (1);
+
+ if (query.Length == 0)
+ return new QueryStringCollection (1);
+
+ if (query == "?")
+ return new QueryStringCollection (1);
+
+ if (query[0] == '?')
+ query = query.Substring (1);
+
+ if (encoding == null)
+ encoding = Encoding.UTF8;
+
+ var ret = new QueryStringCollection ();
+
+ foreach (var component in query.Split ('&')) {
+ var len = component.Length;
+
+ if (len == 0)
+ continue;
+
+ if (component == "=")
+ continue;
+
+ string name = null;
+ string val = null;
+
+ var idx = component.IndexOf ('=');
+
+ if (idx < 0) {
+ val = component.UrlDecode (encoding);
+ }
+ else if (idx == 0) {
+ val = component.Substring (1).UrlDecode (encoding);
+ }
+ else {
+ name = component.Substring (0, idx).UrlDecode (encoding);
+
+ var start = idx + 1;
+ val = start < len
+ ? component.Substring (start).UrlDecode (encoding)
+ : String.Empty;
+ }
+
+ ret.Add (name, val);
+ }
+
+ return ret;
+ }
+
+ public override string ToString ()
+ {
+ if (Count == 0)
+ return String.Empty;
+
+ var buff = new StringBuilder ();
+
+ var fmt = "{0}={1}&";
+
+ foreach (var key in AllKeys)
+ buff.AppendFormat (fmt, key, this[key]);
+
+ buff.Length--;
+
+ return buff.ToString ();
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/QueryStringCollection.cs.meta b/Assets/External/websocket-sharp/Net/QueryStringCollection.cs.meta
new file mode 100644
index 00000000..916eff21
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/QueryStringCollection.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 9f634e4aee6608d4fbe4b35758b4a3ce
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/ReadBufferState.cs b/Assets/External/websocket-sharp/Net/ReadBufferState.cs
new file mode 100644
index 00000000..bf0de884
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/ReadBufferState.cs
@@ -0,0 +1,129 @@
+#region License
+/*
+ * ReadBufferState.cs
+ *
+ * This code is derived from ChunkedInputStream.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2014-2023 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+using System;
+
+namespace WebSocketSharp.Net
+{
+ internal class ReadBufferState
+ {
+ #region Private Fields
+
+ private HttpStreamAsyncResult _asyncResult;
+ private byte[] _buffer;
+ private int _count;
+ private int _initialCount;
+ private int _offset;
+
+ #endregion
+
+ #region Public Constructors
+
+ public ReadBufferState (
+ byte[] buffer,
+ int offset,
+ int count,
+ HttpStreamAsyncResult asyncResult
+ )
+ {
+ _buffer = buffer;
+ _offset = offset;
+ _count = count;
+ _asyncResult = asyncResult;
+
+ _initialCount = count;
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ public HttpStreamAsyncResult AsyncResult {
+ get {
+ return _asyncResult;
+ }
+
+ set {
+ _asyncResult = value;
+ }
+ }
+
+ public byte[] Buffer {
+ get {
+ return _buffer;
+ }
+
+ set {
+ _buffer = value;
+ }
+ }
+
+ public int Count {
+ get {
+ return _count;
+ }
+
+ set {
+ _count = value;
+ }
+ }
+
+ public int InitialCount {
+ get {
+ return _initialCount;
+ }
+
+ set {
+ _initialCount = value;
+ }
+ }
+
+ public int Offset {
+ get {
+ return _offset;
+ }
+
+ set {
+ _offset = value;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/ReadBufferState.cs.meta b/Assets/External/websocket-sharp/Net/ReadBufferState.cs.meta
new file mode 100644
index 00000000..37ad7bc2
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/ReadBufferState.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 548596b3ae6566547ac9012e7121188b
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/RequestStream.cs b/Assets/External/websocket-sharp/Net/RequestStream.cs
new file mode 100644
index 00000000..dd40f920
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/RequestStream.cs
@@ -0,0 +1,354 @@
+#region License
+/*
+ * RequestStream.cs
+ *
+ * This code is derived from RequestStream.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2012-2023 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+using System;
+using System.IO;
+
+namespace WebSocketSharp.Net
+{
+ internal class RequestStream : Stream
+ {
+ #region Private Fields
+
+ private long _bodyLeft;
+ private int _count;
+ private bool _disposed;
+ private byte[] _initialBuffer;
+ private Stream _innerStream;
+ private int _offset;
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal RequestStream (
+ Stream innerStream,
+ byte[] initialBuffer,
+ int offset,
+ int count,
+ long contentLength
+ )
+ {
+ _innerStream = innerStream;
+ _initialBuffer = initialBuffer;
+ _offset = offset;
+ _count = count;
+ _bodyLeft = contentLength;
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal int Count {
+ get {
+ return _count;
+ }
+ }
+
+ internal byte[] InitialBuffer {
+ get {
+ return _initialBuffer;
+ }
+ }
+
+ internal string ObjectName {
+ get {
+ return GetType ().ToString ();
+ }
+ }
+
+ internal int Offset {
+ get {
+ return _offset;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ 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 ();
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private int fillFromInitialBuffer (byte[] buffer, int offset, int count)
+ {
+ // This method returns a int:
+ // - > 0 The number of bytes read from the initial buffer
+ // - 0 No more bytes read from the initial buffer
+ // - -1 No more content data
+
+ if (_bodyLeft == 0)
+ return -1;
+
+ if (_count == 0)
+ return 0;
+
+ if (count > _count)
+ count = _count;
+
+ if (_bodyLeft > 0 && _bodyLeft < count)
+ count = (int) _bodyLeft;
+
+ Buffer.BlockCopy (_initialBuffer, _offset, buffer, offset, count);
+
+ _offset += count;
+ _count -= count;
+
+ if (_bodyLeft > 0)
+ _bodyLeft -= count;
+
+ return count;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public override IAsyncResult BeginRead (
+ byte[] buffer,
+ int offset,
+ int count,
+ AsyncCallback callback,
+ object state
+ )
+ {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (buffer == null)
+ throw new ArgumentNullException ("buffer");
+
+ if (offset < 0) {
+ var msg = "A negative value.";
+
+ throw new ArgumentOutOfRangeException ("offset", msg);
+ }
+
+ if (count < 0) {
+ var msg = "A negative value.";
+
+ throw new ArgumentOutOfRangeException ("count", msg);
+ }
+
+ var len = buffer.Length;
+
+ if (offset + count > len) {
+ var msg = "The sum of offset and count is greater than the length of buffer.";
+
+ throw new ArgumentException (msg);
+ }
+
+ if (count == 0)
+ return _innerStream.BeginRead (buffer, offset, 0, callback, state);
+
+ var nread = fillFromInitialBuffer (buffer, offset, count);
+
+ if (nread != 0) {
+ var ares = new HttpStreamAsyncResult (callback, state);
+
+ ares.Buffer = buffer;
+ ares.Offset = offset;
+ ares.Count = count;
+ ares.SyncRead = nread > 0 ? nread : 0;
+
+ ares.Complete ();
+
+ return ares;
+ }
+
+ if (_bodyLeft > 0 && _bodyLeft < count)
+ count = (int) _bodyLeft;
+
+ return _innerStream.BeginRead (buffer, offset, count, callback, state);
+ }
+
+ public override IAsyncResult BeginWrite (
+ byte[] buffer,
+ int offset,
+ int count,
+ AsyncCallback callback,
+ object state
+ )
+ {
+ throw new NotSupportedException ();
+ }
+
+ public override void Close ()
+ {
+ _disposed = true;
+ }
+
+ public override int EndRead (IAsyncResult asyncResult)
+ {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (asyncResult == null)
+ throw new ArgumentNullException ("asyncResult");
+
+ if (asyncResult is HttpStreamAsyncResult) {
+ var ares = (HttpStreamAsyncResult) asyncResult;
+
+ if (!ares.IsCompleted)
+ ares.AsyncWaitHandle.WaitOne ();
+
+ return ares.SyncRead;
+ }
+
+ var nread = _innerStream.EndRead (asyncResult);
+
+ if (nread > 0 && _bodyLeft > 0)
+ _bodyLeft -= nread;
+
+ return nread;
+ }
+
+ public override void EndWrite (IAsyncResult asyncResult)
+ {
+ throw new NotSupportedException ();
+ }
+
+ public override void Flush ()
+ {
+ }
+
+ public override int Read (byte[] buffer, int offset, int count)
+ {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ if (buffer == null)
+ throw new ArgumentNullException ("buffer");
+
+ if (offset < 0) {
+ var msg = "A negative value.";
+
+ throw new ArgumentOutOfRangeException ("offset", msg);
+ }
+
+ if (count < 0) {
+ var msg = "A negative value.";
+
+ throw new ArgumentOutOfRangeException ("count", msg);
+ }
+
+ var len = buffer.Length;
+
+ if (offset + count > len) {
+ var msg = "The sum of offset and count is greater than the length of buffer.";
+
+ throw new ArgumentException (msg);
+ }
+
+ if (count == 0)
+ return 0;
+
+ var nread = fillFromInitialBuffer (buffer, offset, count);
+
+ if (nread == -1)
+ return 0;
+
+ if (nread > 0)
+ return nread;
+
+ if (_bodyLeft > 0 && _bodyLeft < count)
+ count = (int) _bodyLeft;
+
+ nread = _innerStream.Read (buffer, offset, count);
+
+ if (nread > 0 && _bodyLeft > 0)
+ _bodyLeft -= 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 ();
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/RequestStream.cs.meta b/Assets/External/websocket-sharp/Net/RequestStream.cs.meta
new file mode 100644
index 00000000..e14214f4
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/RequestStream.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 154f802ba52891c42a93181be89345c4
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/ResponseStream.cs b/Assets/External/websocket-sharp/Net/ResponseStream.cs
new file mode 100644
index 00000000..456d1e47
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/ResponseStream.cs
@@ -0,0 +1,416 @@
+#region License
+/*
+ * ResponseStream.cs
+ *
+ * This code is derived from ResponseStream.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2012-2023 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
+
+#region Authors
+/*
+ * Authors:
+ * - Gonzalo Paniagua Javier
+ */
+#endregion
+
+using System;
+using System.IO;
+using System.Text;
+
+namespace WebSocketSharp.Net
+{
+ internal class ResponseStream : Stream
+ {
+ #region Private Fields
+
+ private MemoryStream _bodyBuffer;
+ private static readonly byte[] _crlf;
+ private bool _disposed;
+ private Stream _innerStream;
+ private static readonly byte[] _lastChunk;
+ private static readonly int _maxHeadersLength;
+ private HttpListenerResponse _response;
+ private bool _sendChunked;
+ private Action _write;
+ private Action _writeBody;
+ private Action _writeChunked;
+
+ #endregion
+
+ #region Static Constructor
+
+ static ResponseStream ()
+ {
+ _crlf = new byte[] { 13, 10 }; // "\r\n"
+ _lastChunk = new byte[] { 48, 13, 10, 13, 10 }; // "0\r\n\r\n"
+ _maxHeadersLength = 32768;
+ }
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal ResponseStream (
+ Stream innerStream,
+ HttpListenerResponse response,
+ bool ignoreWriteExceptions
+ )
+ {
+ _innerStream = innerStream;
+ _response = response;
+
+ if (ignoreWriteExceptions) {
+ _write = writeWithoutThrowingException;
+ _writeChunked = writeChunkedWithoutThrowingException;
+ }
+ else {
+ _write = innerStream.Write;
+ _writeChunked = writeChunked;
+ }
+
+ _bodyBuffer = new MemoryStream ();
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal string ObjectName {
+ get {
+ return GetType ().ToString ();
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ public override bool CanRead {
+ get {
+ return false;
+ }
+ }
+
+ public override bool CanSeek {
+ get {
+ return false;
+ }
+ }
+
+ public override bool CanWrite {
+ get {
+ return !_disposed;
+ }
+ }
+
+ public override long Length {
+ get {
+ throw new NotSupportedException ();
+ }
+ }
+
+ public override long Position {
+ get {
+ throw new NotSupportedException ();
+ }
+
+ set {
+ throw new NotSupportedException ();
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private bool flush (bool closing)
+ {
+ if (!_response.HeadersSent) {
+ if (!flushHeaders ())
+ return false;
+
+ _response.HeadersSent = true;
+
+ _sendChunked = _response.SendChunked;
+ _writeBody = _sendChunked ? _writeChunked : _write;
+ }
+
+ flushBody (closing);
+
+ return true;
+ }
+
+ private void flushBody (bool closing)
+ {
+ using (_bodyBuffer) {
+ var len = _bodyBuffer.Length;
+
+ if (len > Int32.MaxValue) {
+ _bodyBuffer.Position = 0;
+
+ var buffLen = 1024;
+ var buff = new byte[buffLen];
+ var nread = 0;
+
+ while (true) {
+ nread = _bodyBuffer.Read (buff, 0, buffLen);
+
+ if (nread <= 0)
+ break;
+
+ _writeBody (buff, 0, nread);
+ }
+ }
+ else if (len > 0) {
+ var buff = _bodyBuffer.GetBuffer ();
+
+ _writeBody (buff, 0, (int) len);
+ }
+ }
+
+ if (!closing) {
+ _bodyBuffer = new MemoryStream ();
+
+ return;
+ }
+
+ if (_sendChunked)
+ _write (_lastChunk, 0, 5);
+
+ _bodyBuffer = null;
+ }
+
+ private bool flushHeaders ()
+ {
+ if (!_response.SendChunked) {
+ if (_response.ContentLength64 != _bodyBuffer.Length)
+ return false;
+ }
+
+ var headers = _response.FullHeaders;
+
+ var stream = new MemoryStream ();
+ var enc = Encoding.UTF8;
+
+ using (var writer = new StreamWriter (stream, enc, 256)) {
+ writer.Write (_response.StatusLine);
+
+ var s = headers.ToStringMultiValue (true);
+
+ writer.Write (s);
+ writer.Flush ();
+
+ var start = enc.GetPreamble ().Length;
+ var len = stream.Length - start;
+
+ if (len > _maxHeadersLength)
+ return false;
+
+ var buff = stream.GetBuffer ();
+
+ _write (buff, start, (int) len);
+ }
+
+ _response.CloseConnection = headers["Connection"] == "close";
+
+ return true;
+ }
+
+ private static byte[] getChunkSizeStringAsBytes (int size)
+ {
+ var fmt = "{0:x}\r\n";
+ var s = String.Format (fmt, size);
+
+ return Encoding.ASCII.GetBytes (s);
+ }
+
+ private void writeChunked (byte[] buffer, int offset, int count)
+ {
+ var size = getChunkSizeStringAsBytes (count);
+
+ _innerStream.Write (size, 0, size.Length);
+ _innerStream.Write (buffer, offset, count);
+ _innerStream.Write (_crlf, 0, 2);
+ }
+
+ private void writeChunkedWithoutThrowingException (
+ byte[] buffer,
+ int offset,
+ int count
+ )
+ {
+ try {
+ writeChunked (buffer, offset, count);
+ }
+ catch {
+ }
+ }
+
+ private void writeWithoutThrowingException (
+ byte[] buffer,
+ int offset,
+ int count
+ )
+ {
+ try {
+ _innerStream.Write (buffer, offset, count);
+ }
+ catch {
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal void Close (bool force)
+ {
+ if (_disposed)
+ return;
+
+ _disposed = true;
+
+ if (!force) {
+ if (flush (true)) {
+ _response.Close ();
+
+ _response = null;
+ _innerStream = null;
+
+ return;
+ }
+
+ _response.CloseConnection = true;
+ }
+
+ if (_sendChunked)
+ _write (_lastChunk, 0, 5);
+
+ _bodyBuffer.Dispose ();
+ _response.Abort ();
+
+ _bodyBuffer = null;
+ _response = null;
+ _innerStream = null;
+ }
+
+ internal void InternalWrite (byte[] buffer, int offset, int count)
+ {
+ _write (buffer, offset, count);
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public override IAsyncResult BeginRead (
+ byte[] buffer,
+ int offset,
+ int count,
+ AsyncCallback callback,
+ object state
+ )
+ {
+ throw new NotSupportedException ();
+ }
+
+ public override IAsyncResult BeginWrite (
+ byte[] buffer,
+ int offset,
+ int count,
+ AsyncCallback callback,
+ object state
+ )
+ {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ return _bodyBuffer.BeginWrite (buffer, offset, count, callback, state);
+ }
+
+ public override void Close ()
+ {
+ Close (false);
+ }
+
+ protected override void Dispose (bool disposing)
+ {
+ Close (!disposing);
+ }
+
+ public override int EndRead (IAsyncResult asyncResult)
+ {
+ throw new NotSupportedException ();
+ }
+
+ public override void EndWrite (IAsyncResult asyncResult)
+ {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ _bodyBuffer.EndWrite (asyncResult);
+ }
+
+ public override void Flush ()
+ {
+ if (_disposed)
+ return;
+
+ var sendChunked = _sendChunked || _response.SendChunked;
+
+ if (!sendChunked)
+ return;
+
+ flush (false);
+ }
+
+ public override int Read (byte[] buffer, int offset, int count)
+ {
+ throw new NotSupportedException ();
+ }
+
+ 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)
+ {
+ if (_disposed)
+ throw new ObjectDisposedException (ObjectName);
+
+ _bodyBuffer.Write (buffer, offset, count);
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/ResponseStream.cs.meta b/Assets/External/websocket-sharp/Net/ResponseStream.cs.meta
new file mode 100644
index 00000000..c1285b09
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/ResponseStream.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: f80d37cc62e523e43a664bce71d36788
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/ServerSslConfiguration.cs b/Assets/External/websocket-sharp/Net/ServerSslConfiguration.cs
new file mode 100644
index 00000000..b4de5d64
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/ServerSslConfiguration.cs
@@ -0,0 +1,239 @@
+#region License
+/*
+ * ServerSslConfiguration.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2014 liryna
+ * Copyright (c) 2014-2024 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
+
+#region Authors
+/*
+ * Authors:
+ * - Liryna
+ */
+#endregion
+
+using System;
+using System.Net.Security;
+using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
+
+namespace WebSocketSharp.Net
+{
+ ///
+ /// Stores the parameters for instances used by
+ /// a server.
+ ///
+ public class ServerSslConfiguration
+ {
+ #region Private Fields
+
+ private bool _checkCertRevocation;
+ private bool _clientCertRequired;
+ private RemoteCertificateValidationCallback _clientCertValidationCallback;
+ private SslProtocols _enabledSslProtocols;
+ private X509Certificate2 _serverCert;
+
+ #endregion
+
+ #region Public Constructors
+
+ ///
+ /// Initializes a new instance of the
+ /// class.
+ ///
+ public ServerSslConfiguration ()
+ {
+ _enabledSslProtocols = SslProtocols.None;
+ }
+
+ ///
+ /// Initializes a new instance of the
+ /// class copying from the specified configuration.
+ ///
+ ///
+ /// A from which to copy.
+ ///
+ ///
+ /// is .
+ ///
+ public ServerSslConfiguration (ServerSslConfiguration configuration)
+ {
+ if (configuration == null)
+ throw new ArgumentNullException ("configuration");
+
+ _checkCertRevocation = configuration._checkCertRevocation;
+ _clientCertRequired = configuration._clientCertRequired;
+ _clientCertValidationCallback = configuration._clientCertValidationCallback;
+ _enabledSslProtocols = configuration._enabledSslProtocols;
+ _serverCert = configuration._serverCert;
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets or sets a value indicating whether the certificate revocation
+ /// list is checked during authentication.
+ ///
+ ///
+ ///
+ /// true if the certificate revocation list is checked during
+ /// authentication; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ public bool CheckCertificateRevocation {
+ get {
+ return _checkCertRevocation;
+ }
+
+ set {
+ _checkCertRevocation = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether each client is asked for
+ /// a certificate for authentication.
+ ///
+ ///
+ ///
+ /// true if each client is asked for a certificate for
+ /// authentication; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ public bool ClientCertificateRequired {
+ get {
+ return _clientCertRequired;
+ }
+
+ set {
+ _clientCertRequired = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the callback used to validate the certificate supplied by
+ /// each client.
+ ///
+ ///
+ /// The certificate is valid if the callback returns true.
+ ///
+ ///
+ ///
+ /// A delegate.
+ ///
+ ///
+ /// It represents the delegate called when the server validates
+ /// the certificate.
+ ///
+ ///
+ /// The default value invokes a method that only returns true.
+ ///
+ ///
+ public RemoteCertificateValidationCallback ClientCertificateValidationCallback {
+ get {
+ if (_clientCertValidationCallback == null)
+ _clientCertValidationCallback = defaultValidateClientCertificate;
+
+ return _clientCertValidationCallback;
+ }
+
+ set {
+ _clientCertValidationCallback = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the enabled versions of the SSL/TLS protocols.
+ ///
+ ///
+ ///
+ /// Any of the enum values.
+ ///
+ ///
+ /// It represents the enabled versions of the SSL/TLS protocols.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ public SslProtocols EnabledSslProtocols {
+ get {
+ return _enabledSslProtocols;
+ }
+
+ set {
+ _enabledSslProtocols = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the certificate used to authenticate the server.
+ ///
+ ///
+ ///
+ /// A that represents an X.509 certificate.
+ ///
+ ///
+ /// if not present.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ public X509Certificate2 ServerCertificate {
+ get {
+ return _serverCert;
+ }
+
+ set {
+ _serverCert = value;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private static bool defaultValidateClientCertificate (
+ object sender,
+ X509Certificate certificate,
+ X509Chain chain,
+ SslPolicyErrors sslPolicyErrors
+ )
+ {
+ return true;
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/ServerSslConfiguration.cs.meta b/Assets/External/websocket-sharp/Net/ServerSslConfiguration.cs.meta
new file mode 100644
index 00000000..f4bdbbee
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/ServerSslConfiguration.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 00dcca2ef788e9149beb201524f880b2
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/WebHeaderCollection.cs b/Assets/External/websocket-sharp/Net/WebHeaderCollection.cs
new file mode 100644
index 00000000..e4115081
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/WebHeaderCollection.cs
@@ -0,0 +1,1907 @@
+#region License
+/*
+ * WebHeaderCollection.cs
+ *
+ * This code is derived from WebHeaderCollection.cs (System.Net) of Mono
+ * (http://www.mono-project.com).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2003 Ximian, Inc. (http://www.ximian.com)
+ * Copyright (c) 2007 Novell, Inc. (http://www.novell.com)
+ * Copyright (c) 2012-2024 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
+
+#region Authors
+/*
+ * Authors:
+ * - Lawrence Pit
+ * - Gonzalo Paniagua Javier
+ * - Miguel de Icaza
+ */
+#endregion
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+using System.Security.Permissions;
+using System.Text;
+
+namespace WebSocketSharp.Net
+{
+ ///
+ /// Provides a collection of the HTTP headers associated with a request or
+ /// response.
+ ///
+ [Serializable]
+ [ComVisible (true)]
+ public class WebHeaderCollection : NameValueCollection, ISerializable
+ {
+ #region Private Fields
+
+ private static readonly Dictionary _headers;
+ private bool _internallyUsed;
+ private HttpHeaderType _state;
+
+ #endregion
+
+ #region Static Constructor
+
+ static WebHeaderCollection ()
+ {
+ _headers =
+ new Dictionary (
+ StringComparer.InvariantCultureIgnoreCase
+ )
+ {
+ {
+ "Accept",
+ new HttpHeaderInfo (
+ "Accept",
+ HttpHeaderType.Request
+ | HttpHeaderType.Restricted
+ | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "AcceptCharset",
+ new HttpHeaderInfo (
+ "Accept-Charset",
+ HttpHeaderType.Request | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "AcceptEncoding",
+ new HttpHeaderInfo (
+ "Accept-Encoding",
+ HttpHeaderType.Request | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "AcceptLanguage",
+ new HttpHeaderInfo (
+ "Accept-Language",
+ HttpHeaderType.Request | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "AcceptRanges",
+ new HttpHeaderInfo (
+ "Accept-Ranges",
+ HttpHeaderType.Response | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "Age",
+ new HttpHeaderInfo (
+ "Age",
+ HttpHeaderType.Response
+ )
+ },
+ {
+ "Allow",
+ new HttpHeaderInfo (
+ "Allow",
+ HttpHeaderType.Request
+ | HttpHeaderType.Response
+ | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "Authorization",
+ new HttpHeaderInfo (
+ "Authorization",
+ HttpHeaderType.Request | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "CacheControl",
+ new HttpHeaderInfo (
+ "Cache-Control",
+ HttpHeaderType.Request
+ | HttpHeaderType.Response
+ | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "Connection",
+ new HttpHeaderInfo (
+ "Connection",
+ HttpHeaderType.Request
+ | HttpHeaderType.Response
+ | HttpHeaderType.Restricted
+ | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "ContentEncoding",
+ new HttpHeaderInfo (
+ "Content-Encoding",
+ HttpHeaderType.Request
+ | HttpHeaderType.Response
+ | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "ContentLanguage",
+ new HttpHeaderInfo (
+ "Content-Language",
+ HttpHeaderType.Request
+ | HttpHeaderType.Response
+ | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "ContentLength",
+ new HttpHeaderInfo (
+ "Content-Length",
+ HttpHeaderType.Request
+ | HttpHeaderType.Response
+ | HttpHeaderType.Restricted
+ )
+ },
+ {
+ "ContentLocation",
+ new HttpHeaderInfo (
+ "Content-Location",
+ HttpHeaderType.Request | HttpHeaderType.Response
+ )
+ },
+ {
+ "ContentMd5",
+ new HttpHeaderInfo (
+ "Content-MD5",
+ HttpHeaderType.Request | HttpHeaderType.Response
+ )
+ },
+ {
+ "ContentRange",
+ new HttpHeaderInfo (
+ "Content-Range",
+ HttpHeaderType.Request | HttpHeaderType.Response
+ )
+ },
+ {
+ "ContentType",
+ new HttpHeaderInfo (
+ "Content-Type",
+ HttpHeaderType.Request
+ | HttpHeaderType.Response
+ | HttpHeaderType.Restricted
+ )
+ },
+ {
+ "Cookie",
+ new HttpHeaderInfo (
+ "Cookie",
+ HttpHeaderType.Request
+ )
+ },
+ {
+ "Cookie2",
+ new HttpHeaderInfo (
+ "Cookie2",
+ HttpHeaderType.Request
+ )
+ },
+ {
+ "Date",
+ new HttpHeaderInfo (
+ "Date",
+ HttpHeaderType.Request
+ | HttpHeaderType.Response
+ | HttpHeaderType.Restricted
+ )
+ },
+ {
+ "Expect",
+ new HttpHeaderInfo (
+ "Expect",
+ HttpHeaderType.Request
+ | HttpHeaderType.Restricted
+ | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "Expires",
+ new HttpHeaderInfo (
+ "Expires",
+ HttpHeaderType.Request | HttpHeaderType.Response
+ )
+ },
+ {
+ "ETag",
+ new HttpHeaderInfo (
+ "ETag",
+ HttpHeaderType.Response
+ )
+ },
+ {
+ "From",
+ new HttpHeaderInfo (
+ "From",
+ HttpHeaderType.Request
+ )
+ },
+ {
+ "Host",
+ new HttpHeaderInfo (
+ "Host",
+ HttpHeaderType.Request | HttpHeaderType.Restricted
+ )
+ },
+ {
+ "IfMatch",
+ new HttpHeaderInfo (
+ "If-Match",
+ HttpHeaderType.Request | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "IfModifiedSince",
+ new HttpHeaderInfo (
+ "If-Modified-Since",
+ HttpHeaderType.Request | HttpHeaderType.Restricted
+ )
+ },
+ {
+ "IfNoneMatch",
+ new HttpHeaderInfo (
+ "If-None-Match",
+ HttpHeaderType.Request | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "IfRange",
+ new HttpHeaderInfo (
+ "If-Range",
+ HttpHeaderType.Request
+ )
+ },
+ {
+ "IfUnmodifiedSince",
+ new HttpHeaderInfo (
+ "If-Unmodified-Since",
+ HttpHeaderType.Request
+ )
+ },
+ {
+ "KeepAlive",
+ new HttpHeaderInfo (
+ "Keep-Alive",
+ HttpHeaderType.Request
+ | HttpHeaderType.Response
+ | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "LastModified",
+ new HttpHeaderInfo (
+ "Last-Modified",
+ HttpHeaderType.Request | HttpHeaderType.Response
+ )
+ },
+ {
+ "Location",
+ new HttpHeaderInfo (
+ "Location",
+ HttpHeaderType.Response
+ )
+ },
+ {
+ "MaxForwards",
+ new HttpHeaderInfo (
+ "Max-Forwards",
+ HttpHeaderType.Request
+ )
+ },
+ {
+ "Pragma",
+ new HttpHeaderInfo (
+ "Pragma",
+ HttpHeaderType.Request | HttpHeaderType.Response
+ )
+ },
+ {
+ "ProxyAuthenticate",
+ new HttpHeaderInfo (
+ "Proxy-Authenticate",
+ HttpHeaderType.Response | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "ProxyAuthorization",
+ new HttpHeaderInfo (
+ "Proxy-Authorization",
+ HttpHeaderType.Request
+ )
+ },
+ {
+ "ProxyConnection",
+ new HttpHeaderInfo (
+ "Proxy-Connection",
+ HttpHeaderType.Request
+ | HttpHeaderType.Response
+ | HttpHeaderType.Restricted
+ )
+ },
+ {
+ "Public",
+ new HttpHeaderInfo (
+ "Public",
+ HttpHeaderType.Response | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "Range",
+ new HttpHeaderInfo (
+ "Range",
+ HttpHeaderType.Request
+ | HttpHeaderType.Restricted
+ | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "Referer",
+ new HttpHeaderInfo (
+ "Referer",
+ HttpHeaderType.Request | HttpHeaderType.Restricted
+ )
+ },
+ {
+ "RetryAfter",
+ new HttpHeaderInfo (
+ "Retry-After",
+ HttpHeaderType.Response
+ )
+ },
+ {
+ "SecWebSocketAccept",
+ new HttpHeaderInfo (
+ "Sec-WebSocket-Accept",
+ HttpHeaderType.Response | HttpHeaderType.Restricted
+ )
+ },
+ {
+ "SecWebSocketExtensions",
+ new HttpHeaderInfo (
+ "Sec-WebSocket-Extensions",
+ HttpHeaderType.Request
+ | HttpHeaderType.Response
+ | HttpHeaderType.Restricted
+ | HttpHeaderType.MultiValueInRequest
+ )
+ },
+ {
+ "SecWebSocketKey",
+ new HttpHeaderInfo (
+ "Sec-WebSocket-Key",
+ HttpHeaderType.Request | HttpHeaderType.Restricted
+ )
+ },
+ {
+ "SecWebSocketProtocol",
+ new HttpHeaderInfo (
+ "Sec-WebSocket-Protocol",
+ HttpHeaderType.Request
+ | HttpHeaderType.Response
+ | HttpHeaderType.MultiValueInRequest
+ )
+ },
+ {
+ "SecWebSocketVersion",
+ new HttpHeaderInfo (
+ "Sec-WebSocket-Version",
+ HttpHeaderType.Request
+ | HttpHeaderType.Response
+ | HttpHeaderType.Restricted
+ | HttpHeaderType.MultiValueInResponse
+ )
+ },
+ {
+ "Server",
+ new HttpHeaderInfo (
+ "Server",
+ HttpHeaderType.Response
+ )
+ },
+ {
+ "SetCookie",
+ new HttpHeaderInfo (
+ "Set-Cookie",
+ HttpHeaderType.Response | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "SetCookie2",
+ new HttpHeaderInfo (
+ "Set-Cookie2",
+ HttpHeaderType.Response | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "Te",
+ new HttpHeaderInfo (
+ "TE",
+ HttpHeaderType.Request
+ )
+ },
+ {
+ "Trailer",
+ new HttpHeaderInfo (
+ "Trailer",
+ HttpHeaderType.Request | HttpHeaderType.Response
+ )
+ },
+ {
+ "TransferEncoding",
+ new HttpHeaderInfo (
+ "Transfer-Encoding",
+ HttpHeaderType.Request
+ | HttpHeaderType.Response
+ | HttpHeaderType.Restricted
+ | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "Translate",
+ new HttpHeaderInfo (
+ "Translate",
+ HttpHeaderType.Request
+ )
+ },
+ {
+ "Upgrade",
+ new HttpHeaderInfo (
+ "Upgrade",
+ HttpHeaderType.Request
+ | HttpHeaderType.Response
+ | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "UserAgent",
+ new HttpHeaderInfo (
+ "User-Agent",
+ HttpHeaderType.Request | HttpHeaderType.Restricted
+ )
+ },
+ {
+ "Vary",
+ new HttpHeaderInfo (
+ "Vary",
+ HttpHeaderType.Response | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "Via",
+ new HttpHeaderInfo (
+ "Via",
+ HttpHeaderType.Request
+ | HttpHeaderType.Response
+ | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "Warning",
+ new HttpHeaderInfo (
+ "Warning",
+ HttpHeaderType.Request
+ | HttpHeaderType.Response
+ | HttpHeaderType.MultiValue
+ )
+ },
+ {
+ "WwwAuthenticate",
+ new HttpHeaderInfo (
+ "WWW-Authenticate",
+ HttpHeaderType.Response
+ | HttpHeaderType.Restricted
+ | HttpHeaderType.MultiValue
+ )
+ }
+ };
+ }
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal WebHeaderCollection (HttpHeaderType state, bool internallyUsed)
+ {
+ _state = state;
+ _internallyUsed = internallyUsed;
+ }
+
+ #endregion
+
+ #region Protected Constructors
+
+ ///
+ /// Initializes a new instance of the
+ /// class with the specified serialized data.
+ ///
+ ///
+ /// A that contains the serialized
+ /// object data.
+ ///
+ ///
+ /// A that specifies the source for
+ /// the deserialization.
+ ///
+ ///
+ /// An element with the specified name is not found in
+ /// .
+ ///
+ ///
+ /// is .
+ ///
+ protected WebHeaderCollection (
+ SerializationInfo serializationInfo,
+ StreamingContext streamingContext
+ )
+ {
+ if (serializationInfo == null)
+ throw new ArgumentNullException ("serializationInfo");
+
+ try {
+ _internallyUsed = serializationInfo.GetBoolean ("InternallyUsed");
+ _state = (HttpHeaderType) serializationInfo.GetInt32 ("State");
+
+ var cnt = serializationInfo.GetInt32 ("Count");
+
+ for (var i = 0; i < cnt; i++) {
+ base.Add (
+ serializationInfo.GetString (i.ToString ()),
+ serializationInfo.GetString ((cnt + i).ToString ())
+ );
+ }
+ }
+ catch (SerializationException ex) {
+ throw new ArgumentException (ex.Message, "serializationInfo", ex);
+ }
+ }
+
+ #endregion
+
+ #region Public Constructors
+
+ ///
+ /// Initializes a new instance of the
+ /// class.
+ ///
+ public WebHeaderCollection ()
+ {
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal HttpHeaderType State {
+ get {
+ return _state;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets all header names in the collection.
+ ///
+ ///
+ /// An array of that contains all header names in
+ /// the collection.
+ ///
+ public override string[] AllKeys {
+ get {
+ return base.AllKeys;
+ }
+ }
+
+ ///
+ /// Gets the number of headers in the collection.
+ ///
+ ///
+ /// An that represents the number of headers in
+ /// the collection.
+ ///
+ public override int Count {
+ get {
+ return base.Count;
+ }
+ }
+
+ ///
+ /// Gets or sets the specified request header.
+ ///
+ ///
+ /// A that represents the value of the request header.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It specifies the request header to get or set.
+ ///
+ ///
+ ///
+ ///
+ /// is a restricted header.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ ///
+ /// The length of is greater than 65,535
+ /// characters.
+ ///
+ ///
+ /// This instance does not allow the request header.
+ ///
+ public string this[HttpRequestHeader header] {
+ get {
+ var key = header.ToString ();
+ var name = getHeaderName (key);
+
+ return Get (name);
+ }
+
+ set {
+ Add (header, value);
+ }
+ }
+
+ ///
+ /// Gets or sets the specified response header.
+ ///
+ ///
+ /// A that represents the value of the response header.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It specifies the response header to get or set.
+ ///
+ ///
+ ///
+ ///
+ /// is a restricted header.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ ///
+ /// The length of is greater than 65,535
+ /// characters.
+ ///
+ ///
+ /// This instance does not allow the response header.
+ ///
+ public string this[HttpResponseHeader header] {
+ get {
+ var key = header.ToString ();
+ var name = getHeaderName (key);
+
+ return Get (name);
+ }
+
+ set {
+ Add (header, value);
+ }
+ }
+
+ ///
+ /// Gets a collection of header names in the collection.
+ ///
+ ///
+ /// A that contains
+ /// all header names in the collection.
+ ///
+ public override NameObjectCollectionBase.KeysCollection Keys {
+ get {
+ return base.Keys;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private void add (string name, string value, HttpHeaderType headerType)
+ {
+ base.Add (name, value);
+
+ if (_state != HttpHeaderType.Unspecified)
+ return;
+
+ if (headerType == HttpHeaderType.Unspecified)
+ return;
+
+ _state = headerType;
+ }
+
+ private void checkAllowed (HttpHeaderType headerType)
+ {
+ if (_state == HttpHeaderType.Unspecified)
+ return;
+
+ if (headerType == HttpHeaderType.Unspecified)
+ return;
+
+ if (headerType != _state) {
+ var msg = "This instance does not allow the header.";
+
+ throw new InvalidOperationException (msg);
+ }
+ }
+
+ private static string checkName (string name, string paramName)
+ {
+ if (name == null) {
+ var msg = "The name is null.";
+
+ throw new ArgumentNullException (paramName, msg);
+ }
+
+ if (name.Length == 0) {
+ var msg = "The name is an empty string.";
+
+ throw new ArgumentException (msg, paramName);
+ }
+
+ name = name.Trim ();
+
+ if (name.Length == 0) {
+ var msg = "The name is a string of spaces.";
+
+ throw new ArgumentException (msg, paramName);
+ }
+
+ if (!name.IsToken ()) {
+ var msg = "The name contains an invalid character.";
+
+ throw new ArgumentException (msg, paramName);
+ }
+
+ return name;
+ }
+
+ private void checkRestricted (string name, HttpHeaderType headerType)
+ {
+ if (_internallyUsed)
+ return;
+
+ var res = headerType == HttpHeaderType.Response;
+
+ if (isRestricted (name, res)) {
+ var msg = "The header is a restricted header.";
+
+ throw new ArgumentException (msg);
+ }
+ }
+
+ private static string checkValue (string value, string paramName)
+ {
+ if (value == null)
+ return String.Empty;
+
+ value = value.Trim ();
+
+ var len = value.Length;
+
+ if (len == 0)
+ return value;
+
+ if (len > 65535) {
+ var msg = "The length of the value is greater than 65,535 characters.";
+
+ throw new ArgumentOutOfRangeException (paramName, msg);
+ }
+
+ if (!value.IsText ()) {
+ var msg = "The value contains an invalid character.";
+
+ throw new ArgumentException (msg, paramName);
+ }
+
+ return value;
+ }
+
+ private static HttpHeaderInfo getHeaderInfo (string name)
+ {
+ var compType = StringComparison.InvariantCultureIgnoreCase;
+
+ foreach (var headerInfo in _headers.Values) {
+ if (headerInfo.HeaderName.Equals (name, compType))
+ return headerInfo;
+ }
+
+ return null;
+ }
+
+ private static string getHeaderName (string key)
+ {
+ HttpHeaderInfo headerInfo;
+
+ return _headers.TryGetValue (key, out headerInfo)
+ ? headerInfo.HeaderName
+ : null;
+ }
+
+ private static HttpHeaderType getHeaderType (string name)
+ {
+ var headerInfo = getHeaderInfo (name);
+
+ if (headerInfo == null)
+ return HttpHeaderType.Unspecified;
+
+ if (headerInfo.IsRequest) {
+ return !headerInfo.IsResponse
+ ? HttpHeaderType.Request
+ : HttpHeaderType.Unspecified;
+ }
+
+ return headerInfo.IsResponse
+ ? HttpHeaderType.Response
+ : HttpHeaderType.Unspecified;
+ }
+
+ private static bool isMultiValue (string name, bool response)
+ {
+ var headerInfo = getHeaderInfo (name);
+
+ return headerInfo != null && headerInfo.IsMultiValue (response);
+ }
+
+ private static bool isRestricted (string name, bool response)
+ {
+ var headerInfo = getHeaderInfo (name);
+
+ return headerInfo != null && headerInfo.IsRestricted (response);
+ }
+
+ private void set (string name, string value, HttpHeaderType headerType)
+ {
+ base.Set (name, value);
+
+ if (_state != HttpHeaderType.Unspecified)
+ return;
+
+ if (headerType == HttpHeaderType.Unspecified)
+ return;
+
+ _state = headerType;
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal void InternalRemove (string name)
+ {
+ base.Remove (name);
+ }
+
+ internal void InternalSet (string header, bool response)
+ {
+ var idx = header.IndexOf (':');
+
+ if (idx == -1) {
+ var msg = "It does not contain a colon character.";
+
+ throw new ArgumentException (msg, "header");
+ }
+
+ var name = header.Substring (0, idx);
+ var val = idx < header.Length - 1
+ ? header.Substring (idx + 1)
+ : String.Empty;
+
+ name = checkName (name, "header");
+ val = checkValue (val, "header");
+
+ if (isMultiValue (name, response)) {
+ base.Add (name, val);
+
+ return;
+ }
+
+ base.Set (name, val);
+ }
+
+ internal void InternalSet (string name, string value, bool response)
+ {
+ value = checkValue (value, "value");
+
+ if (isMultiValue (name, response)) {
+ base.Add (name, value);
+
+ return;
+ }
+
+ base.Set (name, value);
+ }
+
+ internal string ToStringMultiValue (bool response)
+ {
+ var cnt = Count;
+
+ if (cnt == 0)
+ return "\r\n";
+
+ var buff = new StringBuilder ();
+
+ var fmt = "{0}: {1}\r\n";
+
+ for (var i = 0; i < cnt; i++) {
+ var name = GetKey (i);
+
+ if (isMultiValue (name, response)) {
+ foreach (var val in GetValues (i))
+ buff.AppendFormat (fmt, name, val);
+
+ continue;
+ }
+
+ buff.AppendFormat (fmt, name, Get (i));
+ }
+
+ buff.Append ("\r\n");
+
+ return buff.ToString ();
+ }
+
+ #endregion
+
+ #region Protected Methods
+
+ ///
+ /// Adds a header to the collection without checking if the header is on
+ /// the restricted header list.
+ ///
+ ///
+ /// A that specifies the name of the header to add.
+ ///
+ ///
+ /// A that specifies the value of the header to add.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a string of spaces.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The length of is greater than 65,535
+ /// characters.
+ ///
+ ///
+ /// This instance does not allow the header.
+ ///
+ protected void AddWithoutValidate (string headerName, string headerValue)
+ {
+ headerName = checkName (headerName, "headerName");
+ headerValue = checkValue (headerValue, "headerValue");
+
+ var headerType = getHeaderType (headerName);
+
+ checkAllowed (headerType);
+
+ add (headerName, headerValue, headerType);
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Adds the specified header to the collection.
+ ///
+ ///
+ /// A that specifies the header to add,
+ /// with the name and value separated by a colon character (':').
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// does not contain a colon character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The name part of is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The name part of is a string of spaces.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The name part of contains an invalid
+ /// character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The value part of contains an invalid
+ /// character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a restricted header.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The length of the value part of is greater
+ /// than 65,535 characters.
+ ///
+ ///
+ /// This instance does not allow the header.
+ ///
+ public void Add (string header)
+ {
+ if (header == null)
+ throw new ArgumentNullException ("header");
+
+ var len = header.Length;
+
+ if (len == 0) {
+ var msg = "An empty string.";
+
+ throw new ArgumentException (msg, "header");
+ }
+
+ var idx = header.IndexOf (':');
+
+ if (idx == -1) {
+ var msg = "It does not contain a colon character.";
+
+ throw new ArgumentException (msg, "header");
+ }
+
+ var name = header.Substring (0, idx);
+ var val = idx < len - 1 ? header.Substring (idx + 1) : String.Empty;
+
+ name = checkName (name, "header");
+ val = checkValue (val, "header");
+
+ var headerType = getHeaderType (name);
+
+ checkRestricted (name, headerType);
+ checkAllowed (headerType);
+
+ add (name, val, headerType);
+ }
+
+ ///
+ /// Adds the specified request header with the specified value to
+ /// the collection.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It specifies the request header to add.
+ ///
+ ///
+ ///
+ /// A that specifies the value of the header to add.
+ ///
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a restricted header.
+ ///
+ ///
+ ///
+ /// The length of is greater than 65,535
+ /// characters.
+ ///
+ ///
+ /// This instance does not allow the request header.
+ ///
+ public void Add (HttpRequestHeader header, string value)
+ {
+ value = checkValue (value, "value");
+
+ var key = header.ToString ();
+ var name = getHeaderName (key);
+
+ checkRestricted (name, HttpHeaderType.Request);
+ checkAllowed (HttpHeaderType.Request);
+
+ add (name, value, HttpHeaderType.Request);
+ }
+
+ ///
+ /// Adds the specified response header with the specified value to
+ /// the collection.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It specifies the response header to add.
+ ///
+ ///
+ ///
+ /// A that specifies the value of the header to add.
+ ///
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a restricted header.
+ ///
+ ///
+ ///
+ /// The length of is greater than 65,535
+ /// characters.
+ ///
+ ///
+ /// This instance does not allow the response header.
+ ///
+ public void Add (HttpResponseHeader header, string value)
+ {
+ value = checkValue (value, "value");
+
+ var key = header.ToString ();
+ var name = getHeaderName (key);
+
+ checkRestricted (name, HttpHeaderType.Response);
+ checkAllowed (HttpHeaderType.Response);
+
+ add (name, value, HttpHeaderType.Response);
+ }
+
+ ///
+ /// Adds a header with the specified name and value to the collection.
+ ///
+ ///
+ /// A that specifies the name of the header to add.
+ ///
+ ///
+ /// A that specifies the value of the header to add.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a string of spaces.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a restricted header name.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The length of is greater than 65,535
+ /// characters.
+ ///
+ ///
+ /// This instance does not allow the header.
+ ///
+ public override void Add (string name, string value)
+ {
+ name = checkName (name, "name");
+ value = checkValue (value, "value");
+
+ var headerType = getHeaderType (name);
+
+ checkRestricted (name, headerType);
+ checkAllowed (headerType);
+
+ add (name, value, headerType);
+ }
+
+ ///
+ /// Removes all headers from the collection.
+ ///
+ public override void Clear ()
+ {
+ base.Clear ();
+
+ _state = HttpHeaderType.Unspecified;
+ }
+
+ ///
+ /// Get the value of the header at the specified index in the collection.
+ ///
+ ///
+ /// A that receives the value of the header.
+ ///
+ ///
+ /// An that specifies the zero-based index of the header
+ /// to get.
+ ///
+ ///
+ /// is out of allowable range of indexes for
+ /// the collection.
+ ///
+ public override string Get (int index)
+ {
+ return base.Get (index);
+ }
+
+ ///
+ /// Get the value of the header with the specified name in the collection.
+ ///
+ ///
+ ///
+ /// A that receives the value of the header.
+ ///
+ ///
+ /// if not found.
+ ///
+ ///
+ ///
+ /// A that specifies the name of the header to get.
+ ///
+ public override string Get (string name)
+ {
+ return base.Get (name);
+ }
+
+ ///
+ /// Gets the enumerator used to iterate through the collection.
+ ///
+ ///
+ /// An instance used to iterate through
+ /// the collection.
+ ///
+ public override IEnumerator GetEnumerator ()
+ {
+ return base.GetEnumerator ();
+ }
+
+ ///
+ /// Get the name of the header at the specified index in the collection.
+ ///
+ ///
+ /// A that receives the name of the header.
+ ///
+ ///
+ /// An that specifies the zero-based index of the header
+ /// to get.
+ ///
+ ///
+ /// is out of allowable range of indexes for
+ /// the collection.
+ ///
+ public override string GetKey (int index)
+ {
+ return base.GetKey (index);
+ }
+
+ ///
+ /// Get the values of the header at the specified index in the collection.
+ ///
+ ///
+ ///
+ /// An array of that receives the values of
+ /// the header.
+ ///
+ ///
+ /// if not present.
+ ///
+ ///
+ ///
+ /// An that specifies the zero-based index of the header
+ /// to get.
+ ///
+ ///
+ /// is out of allowable range of indexes for
+ /// the collection.
+ ///
+ public override string[] GetValues (int index)
+ {
+ var vals = base.GetValues (index);
+
+ return vals != null && vals.Length > 0 ? vals : null;
+ }
+
+ ///
+ /// Get the values of the header with the specified name in the collection.
+ ///
+ ///
+ ///
+ /// An array of that receives the values of
+ /// the header.
+ ///
+ ///
+ /// if not present.
+ ///
+ ///
+ ///
+ /// A that specifies the name of the header to get.
+ ///
+ public override string[] GetValues (string name)
+ {
+ var vals = base.GetValues (name);
+
+ return vals != null && vals.Length > 0 ? vals : null;
+ }
+
+ ///
+ /// Populates the specified instance with
+ /// the data needed to serialize the current instance.
+ ///
+ ///
+ /// A that holds the serialized object data.
+ ///
+ ///
+ /// A that specifies the destination for
+ /// the serialization.
+ ///
+ ///
+ /// is .
+ ///
+ [
+ SecurityPermission (
+ SecurityAction.LinkDemand,
+ Flags = SecurityPermissionFlag.SerializationFormatter
+ )
+ ]
+ public override void GetObjectData (
+ SerializationInfo serializationInfo,
+ StreamingContext streamingContext
+ )
+ {
+ if (serializationInfo == null)
+ throw new ArgumentNullException ("serializationInfo");
+
+ serializationInfo.AddValue ("InternallyUsed", _internallyUsed);
+ serializationInfo.AddValue ("State", (int) _state);
+
+ var cnt = Count;
+
+ serializationInfo.AddValue ("Count", cnt);
+
+ for (var i = 0; i < cnt; i++) {
+ serializationInfo.AddValue (i.ToString (), GetKey (i));
+ serializationInfo.AddValue ((cnt + i).ToString (), Get (i));
+ }
+ }
+
+ ///
+ /// Determines whether the specified header can be set for the request.
+ ///
+ ///
+ /// true if the header cannot be set; otherwise, false.
+ ///
+ ///
+ /// A that specifies the name of the header to test.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a string of spaces.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public static bool IsRestricted (string headerName)
+ {
+ return IsRestricted (headerName, false);
+ }
+
+ ///
+ /// Determines whether the specified header can be set for the request or
+ /// the response.
+ ///
+ ///
+ /// true if the header cannot be set; otherwise, false.
+ ///
+ ///
+ /// A that specifies the name of the header to test.
+ ///
+ ///
+ /// A : true if the test is for the response;
+ /// otherwise, false.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a string of spaces.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public static bool IsRestricted (string headerName, bool response)
+ {
+ headerName = checkName (headerName, "headerName");
+
+ return isRestricted (headerName, response);
+ }
+
+ ///
+ /// Implements the interface and raises
+ /// the deserialization event when the deserialization is complete.
+ ///
+ ///
+ /// An instance that represents the source of
+ /// the deserialization event.
+ ///
+ public override void OnDeserialization (object sender)
+ {
+ }
+
+ ///
+ /// Removes the specified request header from the collection.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It specifies the request header to remove.
+ ///
+ ///
+ ///
+ /// is a restricted header.
+ ///
+ ///
+ /// This instance does not allow the request header.
+ ///
+ public void Remove (HttpRequestHeader header)
+ {
+ var key = header.ToString ();
+ var name = getHeaderName (key);
+
+ checkRestricted (name, HttpHeaderType.Request);
+ checkAllowed (HttpHeaderType.Request);
+
+ base.Remove (name);
+ }
+
+ ///
+ /// Removes the specified response header from the collection.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It specifies the response header to remove.
+ ///
+ ///
+ ///
+ /// is a restricted header.
+ ///
+ ///
+ /// This instance does not allow the response header.
+ ///
+ public void Remove (HttpResponseHeader header)
+ {
+ var key = header.ToString ();
+ var name = getHeaderName (key);
+
+ checkRestricted (name, HttpHeaderType.Response);
+ checkAllowed (HttpHeaderType.Response);
+
+ base.Remove (name);
+ }
+
+ ///
+ /// Removes the specified header from the collection.
+ ///
+ ///
+ /// A that specifies the name of the header to remove.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a string of spaces.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a restricted header name.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// This instance does not allow the header.
+ ///
+ public override void Remove (string name)
+ {
+ name = checkName (name, "name");
+
+ var headerType = getHeaderType (name);
+
+ checkRestricted (name, headerType);
+ checkAllowed (headerType);
+
+ base.Remove (name);
+ }
+
+ ///
+ /// Sets the specified request header to the specified value.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It specifies the request header to set.
+ ///
+ ///
+ ///
+ /// A that specifies the value of the request header
+ /// to set.
+ ///
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a restricted header.
+ ///
+ ///
+ ///
+ /// The length of is greater than 65,535
+ /// characters.
+ ///
+ ///
+ /// This instance does not allow the request header.
+ ///
+ public void Set (HttpRequestHeader header, string value)
+ {
+ value = checkValue (value, "value");
+
+ var key = header.ToString ();
+ var name = getHeaderName (key);
+
+ checkRestricted (name, HttpHeaderType.Request);
+ checkAllowed (HttpHeaderType.Request);
+
+ set (name, value, HttpHeaderType.Request);
+ }
+
+ ///
+ /// Sets the specified response header to the specified value.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It specifies the response header to set.
+ ///
+ ///
+ ///
+ /// A that specifies the value of the response header
+ /// to set.
+ ///
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a restricted header.
+ ///
+ ///
+ ///
+ /// The length of is greater than 65,535
+ /// characters.
+ ///
+ ///
+ /// This instance does not allow the response header.
+ ///
+ public void Set (HttpResponseHeader header, string value)
+ {
+ value = checkValue (value, "value");
+
+ var key = header.ToString ();
+ var name = getHeaderName (key);
+
+ checkRestricted (name, HttpHeaderType.Response);
+ checkAllowed (HttpHeaderType.Response);
+
+ set (name, value, HttpHeaderType.Response);
+ }
+
+ ///
+ /// Sets the specified header to the specified value.
+ ///
+ ///
+ /// A that specifies the name of the header to set.
+ ///
+ ///
+ /// A that specifies the value of the header to set.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a string of spaces.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a restricted header name.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The length of is greater than 65,535
+ /// characters.
+ ///
+ ///
+ /// This instance does not allow the header.
+ ///
+ public override void Set (string name, string value)
+ {
+ name = checkName (name, "name");
+ value = checkValue (value, "value");
+
+ var headerType = getHeaderType (name);
+
+ checkRestricted (name, headerType);
+ checkAllowed (headerType);
+
+ set (name, value, headerType);
+ }
+
+ ///
+ /// Converts the current instance to an array of byte.
+ ///
+ ///
+ /// An array of converted from a string that represents
+ /// the current instance.
+ ///
+ public byte[] ToByteArray ()
+ {
+ var s = ToString ();
+
+ return Encoding.UTF8.GetBytes (s);
+ }
+
+ ///
+ /// Returns a string that represents the current instance.
+ ///
+ ///
+ /// A that represents all headers in the collection.
+ ///
+ public override string ToString ()
+ {
+ var cnt = Count;
+
+ if (cnt == 0)
+ return "\r\n";
+
+ var buff = new StringBuilder ();
+
+ var fmt = "{0}: {1}\r\n";
+
+ for (var i = 0; i < cnt; i++)
+ buff.AppendFormat (fmt, GetKey (i), Get (i));
+
+ buff.Append ("\r\n");
+
+ return buff.ToString ();
+ }
+
+ #endregion
+
+ #region Explicit Interface Implementations
+
+ ///
+ /// Populates the specified instance with
+ /// the data needed to serialize the current instance.
+ ///
+ ///
+ /// A that holds the serialized object data.
+ ///
+ ///
+ /// A that specifies the destination for
+ /// the serialization.
+ ///
+ ///
+ /// is .
+ ///
+ [
+ SecurityPermission (
+ SecurityAction.LinkDemand,
+ Flags = SecurityPermissionFlag.SerializationFormatter,
+ SerializationFormatter = true
+ )
+ ]
+ void ISerializable.GetObjectData (
+ SerializationInfo serializationInfo,
+ StreamingContext streamingContext
+ )
+ {
+ GetObjectData (serializationInfo, streamingContext);
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/WebHeaderCollection.cs.meta b/Assets/External/websocket-sharp/Net/WebHeaderCollection.cs.meta
new file mode 100644
index 00000000..4cb4b7ab
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/WebHeaderCollection.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: c5fbeb32f1e31cd4399025806668ea33
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/WebSockets.meta b/Assets/External/websocket-sharp/Net/WebSockets.meta
new file mode 100644
index 00000000..a2c02d46
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/WebSockets.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1ee49f27a7dc7ec4fb4165d53162c565
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/External/websocket-sharp/Net/WebSockets/HttpListenerWebSocketContext.cs b/Assets/External/websocket-sharp/Net/WebSockets/HttpListenerWebSocketContext.cs
new file mode 100644
index 00000000..7e0358d1
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/WebSockets/HttpListenerWebSocketContext.cs
@@ -0,0 +1,406 @@
+#region License
+/*
+ * HttpListenerWebSocketContext.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2025 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.IO;
+using System.Net.Sockets;
+using System.Security.Principal;
+
+namespace WebSocketSharp.Net.WebSockets
+{
+ ///
+ /// Provides the access to the information in a WebSocket handshake request
+ /// to a instance.
+ ///
+ public class HttpListenerWebSocketContext : WebSocketContext
+ {
+ #region Private Fields
+
+ private HttpListenerContext _context;
+ private WebSocket _websocket;
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal HttpListenerWebSocketContext (
+ HttpListenerContext context,
+ string protocol
+ )
+ {
+ _context = context;
+ _websocket = new WebSocket (this, protocol);
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal Logger Log {
+ get {
+ return _context.Listener.Log;
+ }
+ }
+
+ internal Socket Socket {
+ get {
+ return _context.Connection.Socket;
+ }
+ }
+
+ internal Stream Stream {
+ get {
+ return _context.Connection.Stream;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the HTTP cookies included in the handshake request.
+ ///
+ ///
+ ///
+ /// A that contains
+ /// the cookies.
+ ///
+ ///
+ /// An empty collection if not included.
+ ///
+ ///
+ public override CookieCollection CookieCollection {
+ get {
+ return _context.Request.Cookies;
+ }
+ }
+
+ ///
+ /// Gets the HTTP headers included in the handshake request.
+ ///
+ ///
+ /// A that contains the headers.
+ ///
+ public override NameValueCollection Headers {
+ get {
+ return _context.Request.Headers;
+ }
+ }
+
+ ///
+ /// Gets the value of the Host header included in the handshake request.
+ ///
+ ///
+ ///
+ /// A that represents the server host name requested
+ /// by the client.
+ ///
+ ///
+ /// It includes the port number if provided.
+ ///
+ ///
+ public override string Host {
+ get {
+ return _context.Request.UserHostName;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the client is authenticated.
+ ///
+ ///
+ /// true if the client is authenticated; otherwise, false.
+ ///
+ public override bool IsAuthenticated {
+ get {
+ return _context.Request.IsAuthenticated;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the handshake request is sent from
+ /// the local computer.
+ ///
+ ///
+ /// true if the handshake request is sent from the same computer
+ /// as the server; otherwise, false.
+ ///
+ public override bool IsLocal {
+ get {
+ return _context.Request.IsLocal;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether a secure connection is used to send
+ /// the handshake request.
+ ///
+ ///
+ /// true if the connection is secure; otherwise, false.
+ ///
+ public override bool IsSecureConnection {
+ get {
+ return _context.Request.IsSecureConnection;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the request is a WebSocket handshake
+ /// request.
+ ///
+ ///
+ /// true if the request is a WebSocket handshake request; otherwise,
+ /// false.
+ ///
+ public override bool IsWebSocketRequest {
+ get {
+ return _context.Request.IsWebSocketRequest;
+ }
+ }
+
+ ///
+ /// Gets the value of the Origin header included in the handshake request.
+ ///
+ ///
+ ///
+ /// A that represents the value of the Origin header.
+ ///
+ ///
+ /// if not included.
+ ///
+ ///
+ public override string Origin {
+ get {
+ return _context.Request.Headers["Origin"];
+ }
+ }
+
+ ///
+ /// Gets the query string included in the handshake request.
+ ///
+ ///
+ ///
+ /// A that contains the query
+ /// parameters.
+ ///
+ ///
+ /// An empty collection if not included.
+ ///
+ ///
+ public override NameValueCollection QueryString {
+ get {
+ return _context.Request.QueryString;
+ }
+ }
+
+ ///
+ /// Gets the URI requested by the client.
+ ///
+ ///
+ ///
+ /// A that represents the URI parsed from the request.
+ ///
+ ///
+ /// if the URI cannot be parsed.
+ ///
+ ///
+ public override Uri RequestUri {
+ get {
+ return _context.Request.Url;
+ }
+ }
+
+ ///
+ /// Gets the value of the Sec-WebSocket-Key header included in
+ /// the handshake request.
+ ///
+ ///
+ ///
+ /// A that represents the value of
+ /// the Sec-WebSocket-Key header.
+ ///
+ ///
+ /// The value is used to prove that the server received
+ /// a valid WebSocket handshake request.
+ ///
+ ///
+ /// if not included.
+ ///
+ ///
+ public override string SecWebSocketKey {
+ get {
+ return _context.Request.Headers["Sec-WebSocket-Key"];
+ }
+ }
+
+ ///
+ /// Gets the names of the subprotocols from the Sec-WebSocket-Protocol
+ /// header included in the handshake request.
+ ///
+ ///
+ ///
+ /// An
+ /// instance.
+ ///
+ ///
+ /// It provides an enumerator which supports the iteration over
+ /// the collection of the names of the subprotocols.
+ ///
+ ///
+ public override IEnumerable SecWebSocketProtocols {
+ get {
+ var val = _context.Request.Headers["Sec-WebSocket-Protocol"];
+
+ if (val == null || val.Length == 0)
+ yield break;
+
+ foreach (var elm in val.Split (',')) {
+ var protocol = elm.Trim ();
+
+ if (protocol.Length == 0)
+ continue;
+
+ yield return protocol;
+ }
+ }
+ }
+
+ ///
+ /// Gets the value of the Sec-WebSocket-Version header included in
+ /// the handshake request.
+ ///
+ ///
+ ///
+ /// A that represents the WebSocket protocol
+ /// version specified by the client.
+ ///
+ ///
+ /// if not included.
+ ///
+ ///
+ public override string SecWebSocketVersion {
+ get {
+ return _context.Request.Headers["Sec-WebSocket-Version"];
+ }
+ }
+
+ ///
+ /// Gets the endpoint to which the handshake request is sent.
+ ///
+ ///
+ /// A that represents the server
+ /// IP address and port number.
+ ///
+ public override System.Net.IPEndPoint ServerEndPoint {
+ get {
+ return _context.Request.LocalEndPoint;
+ }
+ }
+
+ ///
+ /// Gets the client information.
+ ///
+ ///
+ ///
+ /// A instance that represents identity,
+ /// authentication, and security roles for the client.
+ ///
+ ///
+ /// if the client is not authenticated.
+ ///
+ ///
+ public override IPrincipal User {
+ get {
+ return _context.User;
+ }
+ }
+
+ ///
+ /// Gets the endpoint from which the handshake request is sent.
+ ///
+ ///
+ /// A that represents the client
+ /// IP address and port number.
+ ///
+ public override System.Net.IPEndPoint UserEndPoint {
+ get {
+ return _context.Request.RemoteEndPoint;
+ }
+ }
+
+ ///
+ /// Gets the WebSocket interface used for two-way communication between
+ /// the client and server.
+ ///
+ ///
+ /// A that represents the interface.
+ ///
+ public override WebSocket WebSocket {
+ get {
+ return _websocket;
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal void Close ()
+ {
+ _context.Connection.Close (true);
+ }
+
+ internal void Close (HttpStatusCode code)
+ {
+ _context.Response.StatusCode = (int) code;
+
+ _context.Response.Close ();
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Returns a string that represents the current instance.
+ ///
+ ///
+ /// A that contains the request line and headers
+ /// included in the handshake request.
+ ///
+ public override string ToString ()
+ {
+ return _context.Request.ToString ();
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/WebSockets/HttpListenerWebSocketContext.cs.meta b/Assets/External/websocket-sharp/Net/WebSockets/HttpListenerWebSocketContext.cs.meta
new file mode 100644
index 00000000..94c08097
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/WebSockets/HttpListenerWebSocketContext.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: d538316bf856b3443a372a4daa167ec3
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs b/Assets/External/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs
new file mode 100644
index 00000000..3e8d1277
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs
@@ -0,0 +1,501 @@
+#region License
+/*
+ * TcpListenerWebSocketContext.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2025 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
+
+#region Contributors
+/*
+ * Contributors:
+ * - Liryna
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.IO;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Security.Principal;
+using System.Text;
+
+namespace WebSocketSharp.Net.WebSockets
+{
+ ///
+ /// Provides the access to the information in a WebSocket handshake request
+ /// to a instance.
+ ///
+ internal class TcpListenerWebSocketContext : WebSocketContext
+ {
+ #region Private Fields
+
+ private bool _isSecureConnection;
+ private Logger _log;
+ private NameValueCollection _queryString;
+ private HttpRequest _request;
+ private Uri _requestUri;
+ private Stream _stream;
+ private TcpClient _tcpClient;
+ private IPrincipal _user;
+ private WebSocket _websocket;
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal TcpListenerWebSocketContext (
+ TcpClient tcpClient,
+ string protocol,
+ bool secure,
+ ServerSslConfiguration sslConfig,
+ Logger log
+ )
+ {
+ _tcpClient = tcpClient;
+ _log = log;
+
+ var netStream = tcpClient.GetStream ();
+
+ if (secure) {
+ var sslStream = new SslStream (
+ netStream,
+ false,
+ sslConfig.ClientCertificateValidationCallback
+ );
+
+ sslStream.AuthenticateAsServer (
+ sslConfig.ServerCertificate,
+ sslConfig.ClientCertificateRequired,
+ sslConfig.EnabledSslProtocols,
+ sslConfig.CheckCertificateRevocation
+ );
+
+ _isSecureConnection = true;
+ _stream = sslStream;
+ }
+ else {
+ _stream = netStream;
+ }
+
+ _request = HttpRequest.ReadRequest (_stream, 90000);
+ _websocket = new WebSocket (this, protocol);
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal Logger Log {
+ get {
+ return _log;
+ }
+ }
+
+ internal Socket Socket {
+ get {
+ return _tcpClient.Client;
+ }
+ }
+
+ internal Stream Stream {
+ get {
+ return _stream;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the HTTP cookies included in the handshake request.
+ ///
+ ///
+ ///
+ /// A that contains
+ /// the cookies.
+ ///
+ ///
+ /// An empty collection if not included.
+ ///
+ ///
+ public override CookieCollection CookieCollection {
+ get {
+ return _request.Cookies;
+ }
+ }
+
+ ///
+ /// Gets the HTTP headers included in the handshake request.
+ ///
+ ///
+ /// A that contains the headers.
+ ///
+ public override NameValueCollection Headers {
+ get {
+ return _request.Headers;
+ }
+ }
+
+ ///
+ /// Gets the value of the Host header included in the handshake request.
+ ///
+ ///
+ ///
+ /// A that represents the server host name requested
+ /// by the client.
+ ///
+ ///
+ /// It includes the port number if provided.
+ ///
+ ///
+ public override string Host {
+ get {
+ return _request.Headers["Host"];
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the client is authenticated.
+ ///
+ ///
+ /// true if the client is authenticated; otherwise, false.
+ ///
+ public override bool IsAuthenticated {
+ get {
+ return _user != null;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the handshake request is sent from
+ /// the local computer.
+ ///
+ ///
+ /// true if the handshake request is sent from the same computer
+ /// as the server; otherwise, false.
+ ///
+ public override bool IsLocal {
+ get {
+ return UserEndPoint.Address.IsLocal ();
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether a secure connection is used to send
+ /// the handshake request.
+ ///
+ ///
+ /// true if the connection is secure; otherwise, false.
+ ///
+ public override bool IsSecureConnection {
+ get {
+ return _isSecureConnection;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the request is a WebSocket handshake
+ /// request.
+ ///
+ ///
+ /// true if the request is a WebSocket handshake request; otherwise,
+ /// false.
+ ///
+ public override bool IsWebSocketRequest {
+ get {
+ return _request.IsWebSocketRequest;
+ }
+ }
+
+ ///
+ /// Gets the value of the Origin header included in the handshake request.
+ ///
+ ///
+ ///
+ /// A that represents the value of the Origin header.
+ ///
+ ///
+ /// if not included.
+ ///
+ ///
+ public override string Origin {
+ get {
+ return _request.Headers["Origin"];
+ }
+ }
+
+ ///
+ /// Gets the query string included in the handshake request.
+ ///
+ ///
+ ///
+ /// A that contains the query
+ /// parameters.
+ ///
+ ///
+ /// An empty collection if not included.
+ ///
+ ///
+ public override NameValueCollection QueryString {
+ get {
+ if (_queryString == null) {
+ var uri = RequestUri;
+ var query = uri != null ? uri.Query : null;
+
+ _queryString = QueryStringCollection.Parse (query, Encoding.UTF8);
+ }
+
+ return _queryString;
+ }
+ }
+
+ ///
+ /// Gets the URI requested by the client.
+ ///
+ ///
+ ///
+ /// A that represents the URI parsed from the request.
+ ///
+ ///
+ /// if the URI cannot be parsed.
+ ///
+ ///
+ public override Uri RequestUri {
+ get {
+ if (_requestUri == null) {
+ _requestUri = HttpUtility.CreateRequestUrl (
+ _request.RequestTarget,
+ _request.Headers["Host"],
+ _request.IsWebSocketRequest,
+ _isSecureConnection
+ );
+ }
+
+ return _requestUri;
+ }
+ }
+
+ ///
+ /// Gets the value of the Sec-WebSocket-Key header included in
+ /// the handshake request.
+ ///
+ ///
+ ///
+ /// A that represents the value of
+ /// the Sec-WebSocket-Key header.
+ ///
+ ///
+ /// The value is used to prove that the server received
+ /// a valid WebSocket handshake request.
+ ///
+ ///
+ /// if not included.
+ ///
+ ///
+ public override string SecWebSocketKey {
+ get {
+ return _request.Headers["Sec-WebSocket-Key"];
+ }
+ }
+
+ ///
+ /// Gets the names of the subprotocols from the Sec-WebSocket-Protocol
+ /// header included in the handshake request.
+ ///
+ ///
+ ///
+ /// An
+ /// instance.
+ ///
+ ///
+ /// It provides an enumerator which supports the iteration over
+ /// the collection of the names of the subprotocols.
+ ///
+ ///
+ public override IEnumerable SecWebSocketProtocols {
+ get {
+ var val = _request.Headers["Sec-WebSocket-Protocol"];
+
+ if (val == null || val.Length == 0)
+ yield break;
+
+ foreach (var elm in val.Split (',')) {
+ var protocol = elm.Trim ();
+
+ if (protocol.Length == 0)
+ continue;
+
+ yield return protocol;
+ }
+ }
+ }
+
+ ///
+ /// Gets the value of the Sec-WebSocket-Version header included in
+ /// the handshake request.
+ ///
+ ///
+ ///
+ /// A that represents the WebSocket protocol
+ /// version specified by the client.
+ ///
+ ///
+ /// if not included.
+ ///
+ ///
+ public override string SecWebSocketVersion {
+ get {
+ return _request.Headers["Sec-WebSocket-Version"];
+ }
+ }
+
+ ///
+ /// Gets the endpoint to which the handshake request is sent.
+ ///
+ ///
+ /// A that represents the server
+ /// IP address and port number.
+ ///
+ public override System.Net.IPEndPoint ServerEndPoint {
+ get {
+ return (System.Net.IPEndPoint) _tcpClient.Client.LocalEndPoint;
+ }
+ }
+
+ ///
+ /// Gets the client information.
+ ///
+ ///
+ ///
+ /// A instance that represents identity,
+ /// authentication, and security roles for the client.
+ ///
+ ///
+ /// if the client is not authenticated.
+ ///
+ ///
+ public override IPrincipal User {
+ get {
+ return _user;
+ }
+ }
+
+ ///
+ /// Gets the endpoint from which the handshake request is sent.
+ ///
+ ///
+ /// A that represents the client
+ /// IP address and port number.
+ ///
+ public override System.Net.IPEndPoint UserEndPoint {
+ get {
+ return (System.Net.IPEndPoint) _tcpClient.Client.RemoteEndPoint;
+ }
+ }
+
+ ///
+ /// Gets the WebSocket interface used for two-way communication between
+ /// the client and server.
+ ///
+ ///
+ /// A that represents the interface.
+ ///
+ public override WebSocket WebSocket {
+ get {
+ return _websocket;
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal void Close ()
+ {
+ _stream.Close ();
+ _tcpClient.Close ();
+ }
+
+ internal void Close (HttpStatusCode code)
+ {
+ HttpResponse.CreateCloseResponse (code).WriteTo (_stream);
+
+ _stream.Close ();
+ _tcpClient.Close ();
+ }
+
+ internal void SendAuthenticationChallenge (string challenge)
+ {
+ HttpResponse.CreateUnauthorizedResponse (challenge).WriteTo (_stream);
+
+ _request = HttpRequest.ReadRequest (_stream, 15000);
+ }
+
+ internal bool SetUser (
+ AuthenticationSchemes scheme,
+ string realm,
+ Func credentialsFinder
+ )
+ {
+ var user = HttpUtility.CreateUser (
+ _request.Headers["Authorization"],
+ scheme,
+ realm,
+ _request.HttpMethod,
+ credentialsFinder
+ );
+
+ if (user == null)
+ return false;
+
+ if (!user.Identity.IsAuthenticated)
+ return false;
+
+ _user = user;
+
+ return true;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Returns a string that represents the current instance.
+ ///
+ ///
+ /// A that contains the request line and headers
+ /// included in the handshake request.
+ ///
+ public override string ToString ()
+ {
+ return _request.ToString ();
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs.meta b/Assets/External/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs.meta
new file mode 100644
index 00000000..28ea5e98
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 7786101eb71dbfb43866febd1c9c8179
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Net/WebSockets/WebSocketContext.cs b/Assets/External/websocket-sharp/Net/WebSockets/WebSocketContext.cs
new file mode 100644
index 00000000..84841f5b
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/WebSockets/WebSocketContext.cs
@@ -0,0 +1,224 @@
+#region License
+/*
+ * WebSocketContext.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2022 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.WebSockets
+{
+ ///
+ /// Exposes the access to the information in a WebSocket handshake request.
+ ///
+ ///
+ /// This class is an abstract class.
+ ///
+ public abstract class WebSocketContext
+ {
+ #region Protected Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected WebSocketContext ()
+ {
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the HTTP cookies included in the handshake request.
+ ///
+ ///
+ /// A that contains
+ /// the cookies.
+ ///
+ public abstract CookieCollection CookieCollection { get; }
+
+ ///
+ /// Gets the HTTP headers included in the handshake request.
+ ///
+ ///
+ /// A that contains the headers.
+ ///
+ public abstract NameValueCollection Headers { get; }
+
+ ///
+ /// Gets the value of the Host header included in the handshake request.
+ ///
+ ///
+ /// A that represents the server host name requested
+ /// by the client.
+ ///
+ public abstract string Host { get; }
+
+ ///
+ /// Gets a value indicating whether the client is authenticated.
+ ///
+ ///
+ /// true if the client is authenticated; otherwise, false.
+ ///
+ public abstract bool IsAuthenticated { get; }
+
+ ///
+ /// Gets a value indicating whether the handshake request is sent from
+ /// the local computer.
+ ///
+ ///
+ /// true if the handshake request is sent from the same computer
+ /// as the server; otherwise, false.
+ ///
+ public abstract bool IsLocal { get; }
+
+ ///
+ /// Gets a value indicating whether a secure connection is used to send
+ /// the handshake request.
+ ///
+ ///
+ /// true if the connection is secure; otherwise, false.
+ ///
+ public abstract bool IsSecureConnection { get; }
+
+ ///
+ /// Gets a value indicating whether the request is a WebSocket handshake
+ /// request.
+ ///
+ ///
+ /// true if the request is a WebSocket handshake request; otherwise,
+ /// false.
+ ///
+ public abstract bool IsWebSocketRequest { get; }
+
+ ///
+ /// Gets the value of the Origin header included in the handshake request.
+ ///
+ ///
+ /// A that represents the value of the Origin header.
+ ///
+ public abstract string Origin { get; }
+
+ ///
+ /// Gets the query string included in the handshake request.
+ ///
+ ///
+ /// A that contains the query parameters.
+ ///
+ public abstract NameValueCollection QueryString { get; }
+
+ ///
+ /// Gets the URI requested by the client.
+ ///
+ ///
+ /// A that represents the URI parsed from the request.
+ ///
+ public abstract Uri RequestUri { get; }
+
+ ///
+ /// Gets the value of the Sec-WebSocket-Key header included in
+ /// the handshake request.
+ ///
+ ///
+ ///
+ /// A that represents the value of
+ /// the Sec-WebSocket-Key header.
+ ///
+ ///
+ /// The value is used to prove that the server received
+ /// a valid WebSocket handshake request.
+ ///
+ ///
+ public abstract string SecWebSocketKey { get; }
+
+ ///
+ /// Gets the names of the subprotocols from the Sec-WebSocket-Protocol
+ /// header included in the handshake request.
+ ///
+ ///
+ ///
+ /// An
+ /// instance.
+ ///
+ ///
+ /// It provides an enumerator which supports the iteration over
+ /// the collection of the names of the subprotocols.
+ ///
+ ///
+ public abstract IEnumerable SecWebSocketProtocols { get; }
+
+ ///
+ /// Gets the value of the Sec-WebSocket-Version header included in
+ /// the handshake request.
+ ///
+ ///
+ /// A that represents the WebSocket protocol
+ /// version specified by the client.
+ ///
+ public abstract string SecWebSocketVersion { get; }
+
+ ///
+ /// Gets the endpoint to which the handshake request is sent.
+ ///
+ ///
+ /// A that represents the server
+ /// IP address and port number.
+ ///
+ public abstract System.Net.IPEndPoint ServerEndPoint { get; }
+
+ ///
+ /// Gets the client information.
+ ///
+ ///
+ /// A instance that represents identity,
+ /// authentication, and security roles for the client.
+ ///
+ public abstract IPrincipal User { get; }
+
+ ///
+ /// Gets the endpoint from which the handshake request is sent.
+ ///
+ ///
+ /// A that represents the client
+ /// IP address and port number.
+ ///
+ public abstract System.Net.IPEndPoint UserEndPoint { get; }
+
+ ///
+ /// Gets the WebSocket interface used for two-way communication between
+ /// the client and server.
+ ///
+ ///
+ /// A that represents the interface.
+ ///
+ public abstract WebSocket WebSocket { get; }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Net/WebSockets/WebSocketContext.cs.meta b/Assets/External/websocket-sharp/Net/WebSockets/WebSocketContext.cs.meta
new file mode 100644
index 00000000..7f0c5bca
--- /dev/null
+++ b/Assets/External/websocket-sharp/Net/WebSockets/WebSocketContext.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 73dc73292914bc443a4a0157217617bc
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Opcode.cs b/Assets/External/websocket-sharp/Opcode.cs
new file mode 100644
index 00000000..06da1b5e
--- /dev/null
+++ b/Assets/External/websocket-sharp/Opcode.cs
@@ -0,0 +1,68 @@
+#region License
+/*
+ * Opcode.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2025 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;
+
+namespace WebSocketSharp
+{
+ ///
+ /// Indicates the WebSocket frame type.
+ ///
+ ///
+ /// The values of this enumeration are defined in
+ ///
+ /// Section 5.2 of RFC 6455.
+ ///
+ internal enum Opcode
+ {
+ ///
+ /// Equivalent to numeric value 0. Indicates continuation frame.
+ ///
+ Cont = 0x0,
+ ///
+ /// Equivalent to numeric value 1. Indicates text frame.
+ ///
+ Text = 0x1,
+ ///
+ /// Equivalent to numeric value 2. Indicates binary frame.
+ ///
+ Binary = 0x2,
+ ///
+ /// Equivalent to numeric value 8. Indicates connection close frame.
+ ///
+ Close = 0x8,
+ ///
+ /// Equivalent to numeric value 9. Indicates ping frame.
+ ///
+ Ping = 0x9,
+ ///
+ /// Equivalent to numeric value 10. Indicates pong frame.
+ ///
+ Pong = 0xa
+ }
+}
diff --git a/Assets/External/websocket-sharp/Opcode.cs.meta b/Assets/External/websocket-sharp/Opcode.cs.meta
new file mode 100644
index 00000000..b8e1abb8
--- /dev/null
+++ b/Assets/External/websocket-sharp/Opcode.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: befd25291fd922943ab1a42d89fc133d
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/PayloadData.cs b/Assets/External/websocket-sharp/PayloadData.cs
new file mode 100644
index 00000000..e01fcd27
--- /dev/null
+++ b/Assets/External/websocket-sharp/PayloadData.cs
@@ -0,0 +1,212 @@
+#region License
+/*
+ * PayloadData.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2025 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;
+using System.Collections.Generic;
+
+namespace WebSocketSharp
+{
+ internal class PayloadData : IEnumerable
+ {
+ #region Private Fields
+
+ private byte[] _data;
+ private static readonly byte[] _emptyBytes;
+ private long _extDataLength;
+ private long _length;
+
+ #endregion
+
+ #region Public Fields
+
+ ///
+ /// Represents the empty payload data.
+ ///
+ public static readonly PayloadData Empty;
+
+ ///
+ /// Represents the allowable max length of payload data.
+ ///
+ ///
+ ///
+ /// A is thrown when the length of
+ /// incoming payload data is greater than the value of this field.
+ ///
+ ///
+ /// If you would like to change the value of this field, it must be
+ /// a number between and
+ /// inclusive.
+ ///
+ ///
+ public static readonly ulong MaxLength;
+
+ #endregion
+
+ #region Static Constructor
+
+ static PayloadData ()
+ {
+ _emptyBytes = new byte[0];
+
+ Empty = new PayloadData (_emptyBytes, 0);
+ MaxLength = Int64.MaxValue;
+ }
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal PayloadData (byte[] data)
+ : this (data, data.LongLength)
+ {
+ }
+
+ internal PayloadData (byte[] data, long length)
+ {
+ _data = data;
+ _length = length;
+ }
+
+ internal PayloadData (ushort code, string reason)
+ {
+ _data = code.Append (reason);
+ _length = _data.LongLength;
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal ushort Code {
+ get {
+ return _length >= 2
+ ? _data.SubArray (0, 2).ToUInt16 (ByteOrder.Big)
+ : (ushort) 1005;
+ }
+ }
+
+ internal long ExtensionDataLength {
+ get {
+ return _extDataLength;
+ }
+
+ set {
+ _extDataLength = value;
+ }
+ }
+
+ internal bool HasReservedCode {
+ get {
+ return _length >= 2 && Code.IsReservedStatusCode ();
+ }
+ }
+
+ internal string Reason {
+ get {
+ if (_length <= 2)
+ return String.Empty;
+
+ var bytes = _data.SubArray (2, _length - 2);
+
+ string reason;
+
+ return bytes.TryGetUTF8DecodedString (out reason)
+ ? reason
+ : String.Empty;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ public byte[] ApplicationData {
+ get {
+ return _extDataLength > 0
+ ? _data.SubArray (_extDataLength, _length - _extDataLength)
+ : _data;
+ }
+ }
+
+ public byte[] ExtensionData {
+ get {
+ return _extDataLength > 0
+ ? _data.SubArray (0, _extDataLength)
+ : _emptyBytes;
+ }
+ }
+
+ public ulong Length {
+ get {
+ return (ulong) _length;
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal void Mask (byte[] key)
+ {
+ for (long i = 0; i < _length; i++)
+ _data[i] = (byte) (_data[i] ^ key[i % 4]);
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public IEnumerator GetEnumerator ()
+ {
+ foreach (var b in _data)
+ yield return b;
+ }
+
+ public byte[] ToArray ()
+ {
+ return _data;
+ }
+
+ public override string ToString ()
+ {
+ return BitConverter.ToString (_data);
+ }
+
+ #endregion
+
+ #region Explicit Interface Implementations
+
+ IEnumerator IEnumerable.GetEnumerator ()
+ {
+ return GetEnumerator ();
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/PayloadData.cs.meta b/Assets/External/websocket-sharp/PayloadData.cs.meta
new file mode 100644
index 00000000..db3304a9
--- /dev/null
+++ b/Assets/External/websocket-sharp/PayloadData.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 3a3bad22a27c2534fa57956a869b0eec
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Rsv.cs b/Assets/External/websocket-sharp/Rsv.cs
new file mode 100644
index 00000000..c2a4dea7
--- /dev/null
+++ b/Assets/External/websocket-sharp/Rsv.cs
@@ -0,0 +1,53 @@
+#region License
+/*
+ * Rsv.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2025 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;
+
+namespace WebSocketSharp
+{
+ ///
+ /// Indicates whether each RSV (RSV1, RSV2, and RSV3) of a WebSocket
+ /// frame is non-zero.
+ ///
+ ///
+ /// The values of this enumeration are defined in
+ ///
+ /// Section 5.2 of RFC 6455.
+ ///
+ internal enum Rsv
+ {
+ ///
+ /// Equivalent to numeric value 0. Indicates zero.
+ ///
+ Off = 0x0,
+ ///
+ /// Equivalent to numeric value 1. Indicates non-zero.
+ ///
+ On = 0x1
+ }
+}
diff --git a/Assets/External/websocket-sharp/Rsv.cs.meta b/Assets/External/websocket-sharp/Rsv.cs.meta
new file mode 100644
index 00000000..a364479a
--- /dev/null
+++ b/Assets/External/websocket-sharp/Rsv.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 1d6533743cf8690459edcbaea690d25c
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Server.meta b/Assets/External/websocket-sharp/Server.meta
new file mode 100644
index 00000000..56fc04d2
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 124757fa40da5044786652c061a97954
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/External/websocket-sharp/Server/HttpRequestEventArgs.cs b/Assets/External/websocket-sharp/Server/HttpRequestEventArgs.cs
new file mode 100644
index 00000000..45af3c1e
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/HttpRequestEventArgs.cs
@@ -0,0 +1,266 @@
+#region License
+/*
+ * HttpRequestEventArgs.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2024 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.IO;
+using System.Security.Principal;
+using System.Text;
+using WebSocketSharp.Net;
+
+namespace WebSocketSharp.Server
+{
+ ///
+ /// Represents the event data for the HTTP request events of the
+ /// class.
+ ///
+ ///
+ ///
+ /// An HTTP request event occurs when the
+ /// instance receives an HTTP request.
+ ///
+ ///
+ /// You should access the property if you would
+ /// like to get the request data sent from a client.
+ ///
+ ///
+ /// And you should access the property if you
+ /// would like to get the response data to return to the client.
+ ///
+ ///
+ public class HttpRequestEventArgs : EventArgs
+ {
+ #region Private Fields
+
+ private HttpListenerContext _context;
+ private string _docRootPath;
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal HttpRequestEventArgs (
+ HttpListenerContext context,
+ string documentRootPath
+ )
+ {
+ _context = context;
+ _docRootPath = documentRootPath;
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the request data sent from a client.
+ ///
+ ///
+ /// A that provides the methods and
+ /// properties for the request data.
+ ///
+ public HttpListenerRequest Request {
+ get {
+ return _context.Request;
+ }
+ }
+
+ ///
+ /// Gets the response data to return to the client.
+ ///
+ ///
+ /// A that provides the methods and
+ /// properties for the response data.
+ ///
+ public HttpListenerResponse Response {
+ get {
+ return _context.Response;
+ }
+ }
+
+ ///
+ /// Gets the information for the client.
+ ///
+ ///
+ ///
+ /// A instance that represents identity,
+ /// authentication scheme, and security roles for the client.
+ ///
+ ///
+ /// if the client is not authenticated.
+ ///
+ ///
+ public IPrincipal User {
+ get {
+ return _context.User;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private string createFilePath (string childPath)
+ {
+ childPath = childPath.TrimStart ('/', '\\');
+
+ return new StringBuilder (_docRootPath, 32)
+ .AppendFormat ("/{0}", childPath)
+ .ToString ()
+ .Replace ('\\', '/');
+ }
+
+ private static bool tryReadFile (string path, out byte[] contents)
+ {
+ contents = null;
+
+ if (!File.Exists (path))
+ return false;
+
+ try {
+ contents = File.ReadAllBytes (path);
+ }
+ catch {
+ return false;
+ }
+
+ return true;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Reads the specified file from the document folder of the
+ /// class.
+ ///
+ ///
+ ///
+ /// An array of that receives the contents of
+ /// the file.
+ ///
+ ///
+ /// if the read has failed.
+ ///
+ ///
+ ///
+ /// A that specifies a virtual path to find
+ /// the file from the document folder.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains "..".
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public byte[] ReadFile (string path)
+ {
+ if (path == null)
+ throw new ArgumentNullException ("path");
+
+ if (path.Length == 0)
+ throw new ArgumentException ("An empty string.", "path");
+
+ if (path.Contains ("..")) {
+ var msg = "It contains \"..\".";
+
+ throw new ArgumentException (msg, "path");
+ }
+
+ path = createFilePath (path);
+ byte[] contents;
+
+ tryReadFile (path, out contents);
+
+ return contents;
+ }
+
+ ///
+ /// Tries to read the specified file from the document folder of
+ /// the class.
+ ///
+ ///
+ /// true if the try has succeeded; otherwise, false.
+ ///
+ ///
+ /// A that specifies a virtual path to find
+ /// the file from the document folder.
+ ///
+ ///
+ ///
+ /// When this method returns, an array of that
+ /// receives the contents of the file.
+ ///
+ ///
+ /// if the read has failed.
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains "..".
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public bool TryReadFile (string path, out byte[] contents)
+ {
+ if (path == null)
+ throw new ArgumentNullException ("path");
+
+ if (path.Length == 0)
+ throw new ArgumentException ("An empty string.", "path");
+
+ if (path.Contains ("..")) {
+ var msg = "It contains \"..\".";
+
+ throw new ArgumentException (msg, "path");
+ }
+
+ path = createFilePath (path);
+
+ return tryReadFile (path, out contents);
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Server/HttpRequestEventArgs.cs.meta b/Assets/External/websocket-sharp/Server/HttpRequestEventArgs.cs.meta
new file mode 100644
index 00000000..d0b37536
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/HttpRequestEventArgs.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 15241bd4f65d38f438d262a051f78d44
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Server/HttpServer.cs b/Assets/External/websocket-sharp/Server/HttpServer.cs
new file mode 100644
index 00000000..20910998
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/HttpServer.cs
@@ -0,0 +1,1334 @@
+#region License
+/*
+ * HttpServer.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2024 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
+
+#region Contributors
+/*
+ * Contributors:
+ * - Juan Manuel Lallana
+ * - Liryna
+ * - Rohan Singh
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Net.Sockets;
+using System.Security.Cryptography.X509Certificates;
+using System.Security.Principal;
+using System.Text;
+using System.Threading;
+using WebSocketSharp.Net;
+using WebSocketSharp.Net.WebSockets;
+
+namespace WebSocketSharp.Server
+{
+ ///
+ /// Provides a simple HTTP server.
+ ///
+ ///
+ ///
+ /// The server supports HTTP/1.1 version request and response.
+ ///
+ ///
+ /// Also the server allows to accept WebSocket handshake requests.
+ ///
+ ///
+ /// This class can provide multiple WebSocket services.
+ ///
+ ///
+ public class HttpServer
+ {
+ #region Private Fields
+
+ private System.Net.IPAddress _address;
+ private string _docRootPath;
+ private bool _isSecure;
+ private HttpListener _listener;
+ private Logger _log;
+ private int _port;
+ private Thread _receiveThread;
+ private WebSocketServiceManager _services;
+ private volatile ServerState _state;
+ private object _sync;
+
+ #endregion
+
+ #region Public Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The new instance listens for incoming requests on
+ /// and port 80.
+ ///
+ public HttpServer ()
+ {
+ init ("*", System.Net.IPAddress.Any, 80, false);
+ }
+
+ ///
+ /// Initializes a new instance of the class with
+ /// the specified port.
+ ///
+ ///
+ ///
+ /// The new instance listens for incoming requests on
+ /// and .
+ ///
+ ///
+ /// It provides secure connections if is 443.
+ ///
+ ///
+ ///
+ /// An that specifies the number of the port on which
+ /// to listen.
+ ///
+ ///
+ /// is less than 1 or greater than 65535.
+ ///
+ public HttpServer (int port)
+ : this (port, port == 443)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with
+ /// the specified URL.
+ ///
+ ///
+ ///
+ /// The new instance listens for incoming requests on the IP address and
+ /// port of .
+ ///
+ ///
+ /// Either port 80 or 443 is used if includes
+ /// no port. Port 443 is used if the scheme of
+ /// is https; otherwise, port 80 is used.
+ ///
+ ///
+ /// The new instance provides secure connections if the scheme of
+ /// is https.
+ ///
+ ///
+ ///
+ /// A that specifies the HTTP URL of the server.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is invalid.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public HttpServer (string url)
+ {
+ if (url == null)
+ throw new ArgumentNullException ("url");
+
+ if (url.Length == 0)
+ throw new ArgumentException ("An empty string.", "url");
+
+ Uri uri;
+ string msg;
+
+ if (!tryCreateUri (url, out uri, out msg))
+ throw new ArgumentException (msg, "url");
+
+ var host = uri.GetDnsSafeHost (true);
+ var addr = host.ToIPAddress ();
+
+ if (addr == null) {
+ msg = "The host part could not be converted to an IP address.";
+
+ throw new ArgumentException (msg, "url");
+ }
+
+ if (!addr.IsLocal ()) {
+ msg = "The IP address of the host is not a local IP address.";
+
+ throw new ArgumentException (msg, "url");
+ }
+
+ init (host, addr, uri.Port, uri.Scheme == "https");
+ }
+
+ ///
+ /// Initializes a new instance of the class with
+ /// the specified port and boolean if secure or not.
+ ///
+ ///
+ /// The new instance listens for incoming requests on
+ /// and .
+ ///
+ ///
+ /// An that specifies the number of the port on which
+ /// to listen.
+ ///
+ ///
+ /// A : true if the new instance provides
+ /// secure connections; otherwise, false.
+ ///
+ ///
+ /// is less than 1 or greater than 65535.
+ ///
+ public HttpServer (int port, bool secure)
+ {
+ if (!port.IsPortNumber ()) {
+ var msg = "Less than 1 or greater than 65535.";
+
+ throw new ArgumentOutOfRangeException ("port", msg);
+ }
+
+ init ("*", System.Net.IPAddress.Any, port, secure);
+ }
+
+ ///
+ /// Initializes a new instance of the class with
+ /// the specified IP address and port.
+ ///
+ ///
+ ///
+ /// The new instance listens for incoming requests on
+ /// and .
+ ///
+ ///
+ /// It provides secure connections if is 443.
+ ///
+ ///
+ ///
+ /// A that specifies the local IP
+ /// address on which to listen.
+ ///
+ ///
+ /// An that specifies the number of the port on which
+ /// to listen.
+ ///
+ ///
+ /// is not a local IP address.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// is less than 1 or greater than 65535.
+ ///
+ public HttpServer (System.Net.IPAddress address, int port)
+ : this (address, port, port == 443)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with
+ /// the specified IP address, port, and boolean if secure or not.
+ ///
+ ///
+ /// The new instance listens for incoming requests on
+ /// and .
+ ///
+ ///
+ /// A that specifies the local IP
+ /// address on which to listen.
+ ///
+ ///
+ /// An that specifies the number of the port on which
+ /// to listen.
+ ///
+ ///
+ /// A : true if the new instance provides
+ /// secure connections; otherwise, false.
+ ///
+ ///
+ /// is not a local IP address.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// is less than 1 or greater than 65535.
+ ///
+ public HttpServer (System.Net.IPAddress address, int port, bool secure)
+ {
+ if (address == null)
+ throw new ArgumentNullException ("address");
+
+ if (!address.IsLocal ()) {
+ var msg = "Not a local IP address.";
+
+ throw new ArgumentException (msg, "address");
+ }
+
+ if (!port.IsPortNumber ()) {
+ var msg = "Less than 1 or greater than 65535.";
+
+ throw new ArgumentOutOfRangeException ("port", msg);
+ }
+
+ init (address.ToString (true), address, port, secure);
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the IP address of the server.
+ ///
+ ///
+ /// A that represents the local IP
+ /// address on which to listen for incoming requests.
+ ///
+ public System.Net.IPAddress Address {
+ get {
+ return _address;
+ }
+ }
+
+ ///
+ /// Gets or sets the scheme used to authenticate the clients.
+ ///
+ ///
+ /// The set operation works if the current state of the server is
+ /// Ready or Stop.
+ ///
+ ///
+ ///
+ /// One of the
+ /// enum values.
+ ///
+ ///
+ /// It represents the scheme used to authenticate the clients.
+ ///
+ ///
+ /// The default value is
+ /// .
+ ///
+ ///
+ public AuthenticationSchemes AuthenticationSchemes {
+ get {
+ return _listener.AuthenticationSchemes;
+ }
+
+ set {
+ lock (_sync) {
+ if (!canSet ())
+ return;
+
+ _listener.AuthenticationSchemes = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the path to the document folder of the server.
+ ///
+ ///
+ /// The set operation works if the current state of the server is
+ /// Ready or Stop.
+ ///
+ ///
+ ///
+ /// A that represents a path to the folder
+ /// from which to find the requested file.
+ ///
+ ///
+ /// / or \ is trimmed from the end of the value if present.
+ ///
+ ///
+ /// The default value is "./Public".
+ ///
+ ///
+ ///
+ ///
+ /// The value specified for a set operation is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The value specified for a set operation is an absolute root.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The value specified for a set operation is an invalid path string.
+ ///
+ ///
+ ///
+ /// The value specified for a set operation is .
+ ///
+ public string DocumentRootPath {
+ get {
+ return _docRootPath;
+ }
+
+ set {
+ if (value == null)
+ throw new ArgumentNullException ("value");
+
+ if (value.Length == 0)
+ throw new ArgumentException ("An empty string.", "value");
+
+ value = value.TrimSlashOrBackslashFromEnd ();
+
+ if (value == "/")
+ throw new ArgumentException ("An absolute root.", "value");
+
+ if (value == "\\")
+ throw new ArgumentException ("An absolute root.", "value");
+
+ if (value.Length == 2 && value[1] == ':')
+ throw new ArgumentException ("An absolute root.", "value");
+
+ string full = null;
+
+ try {
+ full = Path.GetFullPath (value);
+ }
+ catch (Exception ex) {
+ throw new ArgumentException ("An invalid path string.", "value", ex);
+ }
+
+ if (full == "/")
+ throw new ArgumentException ("An absolute root.", "value");
+
+ full = full.TrimSlashOrBackslashFromEnd ();
+
+ if (full.Length == 2 && full[1] == ':')
+ throw new ArgumentException ("An absolute root.", "value");
+
+ lock (_sync) {
+ if (!canSet ())
+ return;
+
+ _docRootPath = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the server has started.
+ ///
+ ///
+ /// true if the server has started; otherwise, false.
+ ///
+ public bool IsListening {
+ get {
+ return _state == ServerState.Start;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the server provides secure connections.
+ ///
+ ///
+ /// true if the server provides secure connections; otherwise,
+ /// false.
+ ///
+ public bool IsSecure {
+ get {
+ return _isSecure;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the server cleans up
+ /// the inactive sessions periodically.
+ ///
+ ///
+ /// The set operation works if the current state of the server is
+ /// Ready or Stop.
+ ///
+ ///
+ ///
+ /// true if the server cleans up the inactive sessions
+ /// every 60 seconds; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ public bool KeepClean {
+ get {
+ return _services.KeepClean;
+ }
+
+ set {
+ _services.KeepClean = value;
+ }
+ }
+
+ ///
+ /// Gets the logging function for the server.
+ ///
+ ///
+ /// The default logging level is .
+ ///
+ ///
+ /// A that provides the logging function.
+ ///
+ public Logger Log {
+ get {
+ return _log;
+ }
+ }
+
+ ///
+ /// Gets the port of the server.
+ ///
+ ///
+ /// An that represents the number of the port on which
+ /// to listen for incoming requests.
+ ///
+ public int Port {
+ get {
+ return _port;
+ }
+ }
+
+ ///
+ /// Gets or sets the name of the realm associated with the server.
+ ///
+ ///
+ /// The set operation works if the current state of the server is
+ /// Ready or Stop.
+ ///
+ ///
+ ///
+ /// A that represents the name of the realm.
+ ///
+ ///
+ /// "SECRET AREA" is used as the name of the realm if the value is
+ /// or an empty string.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ public string Realm {
+ get {
+ return _listener.Realm;
+ }
+
+ set {
+ lock (_sync) {
+ if (!canSet ())
+ return;
+
+ _listener.Realm = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the server is allowed to
+ /// be bound to an address that is already in use.
+ ///
+ ///
+ ///
+ /// You should set this property to true if you would like to
+ /// resolve to wait for socket in TIME_WAIT state.
+ ///
+ ///
+ /// The set operation works if the current state of the server is
+ /// Ready or Stop.
+ ///
+ ///
+ ///
+ ///
+ /// true if the server is allowed to be bound to an address
+ /// that is already in use; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ public bool ReuseAddress {
+ get {
+ return _listener.ReuseAddress;
+ }
+
+ set {
+ lock (_sync) {
+ if (!canSet ())
+ return;
+
+ _listener.ReuseAddress = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets the configuration for secure connection.
+ ///
+ ///
+ /// The configuration is used when the server attempts to start,
+ /// so it must be configured before the start method is called.
+ ///
+ ///
+ /// A that represents the
+ /// configuration used to provide secure connections.
+ ///
+ ///
+ /// The server does not provide secure connections.
+ ///
+ public ServerSslConfiguration SslConfiguration {
+ get {
+ if (!_isSecure) {
+ var msg = "The server does not provide secure connections.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _listener.SslConfiguration;
+ }
+ }
+
+ ///
+ /// Gets or sets the delegate called to find the credentials for
+ /// an identity used to authenticate a client.
+ ///
+ ///
+ /// The set operation works if the current state of the server is
+ /// Ready or Stop.
+ ///
+ ///
+ ///
+ /// A
+ /// delegate.
+ ///
+ ///
+ /// It represents the delegate called when the server finds
+ /// the credentials used to authenticate a client.
+ ///
+ ///
+ /// It must return if the credentials
+ /// are not found.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ public Func UserCredentialsFinder {
+ get {
+ return _listener.UserCredentialsFinder;
+ }
+
+ set {
+ lock (_sync) {
+ if (!canSet ())
+ return;
+
+ _listener.UserCredentialsFinder = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the time to wait for the response to the WebSocket
+ /// Ping or Close.
+ ///
+ ///
+ /// The set operation works if the current state of the server is
+ /// Ready or Stop.
+ ///
+ ///
+ ///
+ /// A that represents the time to wait for
+ /// the response.
+ ///
+ ///
+ /// The default value is the same as 1 second.
+ ///
+ ///
+ ///
+ /// The value specified for a set operation is zero or less.
+ ///
+ public TimeSpan WaitTime {
+ get {
+ return _services.WaitTime;
+ }
+
+ set {
+ _services.WaitTime = value;
+ }
+ }
+
+ ///
+ /// Gets the management function for the WebSocket services provided by
+ /// the server.
+ ///
+ ///
+ /// A that manages the WebSocket
+ /// services provided by the server.
+ ///
+ public WebSocketServiceManager WebSocketServices {
+ get {
+ return _services;
+ }
+ }
+
+ #endregion
+
+ #region Public Events
+
+ ///
+ /// Occurs when the server receives an HTTP CONNECT request.
+ ///
+ public event EventHandler OnConnect;
+
+ ///
+ /// Occurs when the server receives an HTTP DELETE request.
+ ///
+ public event EventHandler OnDelete;
+
+ ///
+ /// Occurs when the server receives an HTTP GET request.
+ ///
+ public event EventHandler OnGet;
+
+ ///
+ /// Occurs when the server receives an HTTP HEAD request.
+ ///
+ public event EventHandler OnHead;
+
+ ///
+ /// Occurs when the server receives an HTTP OPTIONS request.
+ ///
+ public event EventHandler OnOptions;
+
+ ///
+ /// Occurs when the server receives an HTTP POST request.
+ ///
+ public event EventHandler OnPost;
+
+ ///
+ /// Occurs when the server receives an HTTP PUT request.
+ ///
+ public event EventHandler OnPut;
+
+ ///
+ /// Occurs when the server receives an HTTP TRACE request.
+ ///
+ public event EventHandler OnTrace;
+
+ #endregion
+
+ #region Private Methods
+
+ private void abort ()
+ {
+ lock (_sync) {
+ if (_state != ServerState.Start)
+ return;
+
+ _state = ServerState.ShuttingDown;
+ }
+
+ try {
+ _services.Stop (1006, String.Empty);
+ }
+ catch (Exception ex) {
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
+ }
+
+ try {
+ _listener.Abort ();
+ }
+ catch (Exception ex) {
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
+ }
+
+ _state = ServerState.Stop;
+ }
+
+ private bool canSet ()
+ {
+ return _state == ServerState.Ready || _state == ServerState.Stop;
+ }
+
+ private bool checkCertificate (out string message)
+ {
+ message = null;
+
+ var byUser = _listener.SslConfiguration.ServerCertificate != null;
+
+ var path = _listener.CertificateFolderPath;
+ var withPort = EndPointListener.CertificateExists (_port, path);
+
+ var either = byUser || withPort;
+
+ if (!either) {
+ message = "There is no server certificate for secure connection.";
+
+ return false;
+ }
+
+ var both = byUser && withPort;
+
+ if (both) {
+ var msg = "The server certificate associated with the port is used.";
+
+ _log.Warn (msg);
+ }
+
+ return true;
+ }
+
+ private static HttpListener createListener (
+ string hostname,
+ int port,
+ bool secure
+ )
+ {
+ var ret = new HttpListener ();
+
+ var fmt = "{0}://{1}:{2}/";
+ var schm = secure ? "https" : "http";
+ var pref = String.Format (fmt, schm, hostname, port);
+
+ ret.Prefixes.Add (pref);
+
+ return ret;
+ }
+
+ private void init (
+ string hostname,
+ System.Net.IPAddress address,
+ int port,
+ bool secure
+ )
+ {
+ _address = address;
+ _port = port;
+ _isSecure = secure;
+
+ _docRootPath = "./Public";
+ _listener = createListener (hostname, port, secure);
+ _log = _listener.Log;
+ _services = new WebSocketServiceManager (_log);
+ _sync = new object ();
+ }
+
+ private void processRequest (HttpListenerContext context)
+ {
+ var method = context.Request.HttpMethod;
+ var evt = method == "GET"
+ ? OnGet
+ : method == "HEAD"
+ ? OnHead
+ : method == "POST"
+ ? OnPost
+ : method == "PUT"
+ ? OnPut
+ : method == "DELETE"
+ ? OnDelete
+ : method == "CONNECT"
+ ? OnConnect
+ : method == "OPTIONS"
+ ? OnOptions
+ : method == "TRACE"
+ ? OnTrace
+ : null;
+
+ if (evt == null) {
+ context.ErrorStatusCode = 501;
+
+ context.SendError ();
+
+ return;
+ }
+
+ var e = new HttpRequestEventArgs (context, _docRootPath);
+
+ evt (this, e);
+
+ context.Response.Close ();
+ }
+
+ private void processRequest (HttpListenerWebSocketContext context)
+ {
+ var uri = context.RequestUri;
+
+ if (uri == null) {
+ context.Close (HttpStatusCode.BadRequest);
+
+ return;
+ }
+
+ var path = uri.AbsolutePath;
+
+ if (path.IndexOfAny (new[] { '%', '+' }) > -1)
+ path = HttpUtility.UrlDecode (path, Encoding.UTF8);
+
+ WebSocketServiceHost host;
+
+ if (!_services.InternalTryGetServiceHost (path, out host)) {
+ context.Close (HttpStatusCode.NotImplemented);
+
+ return;
+ }
+
+ host.StartSession (context);
+ }
+
+ private void receiveRequest ()
+ {
+ while (true) {
+ HttpListenerContext ctx = null;
+
+ try {
+ ctx = _listener.GetContext ();
+
+ ThreadPool.QueueUserWorkItem (
+ state => {
+ try {
+ if (ctx.Request.IsUpgradeRequest ("websocket")) {
+ processRequest (ctx.GetWebSocketContext (null));
+
+ return;
+ }
+
+ processRequest (ctx);
+ }
+ catch (Exception ex) {
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ ctx.Connection.Close (true);
+ }
+ }
+ );
+ }
+ catch (HttpListenerException ex) {
+ if (_state == ServerState.ShuttingDown)
+ return;
+
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ break;
+ }
+ catch (InvalidOperationException ex) {
+ if (_state == ServerState.ShuttingDown)
+ return;
+
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ break;
+ }
+ catch (Exception ex) {
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ if (ctx != null)
+ ctx.Connection.Close (true);
+
+ if (_state == ServerState.ShuttingDown)
+ return;
+
+ break;
+ }
+ }
+
+ abort ();
+ }
+
+ private void start ()
+ {
+ lock (_sync) {
+ if (_state == ServerState.Start || _state == ServerState.ShuttingDown)
+ return;
+
+ if (_isSecure) {
+ string msg;
+
+ if (!checkCertificate (out msg))
+ throw new InvalidOperationException (msg);
+ }
+
+ _services.Start ();
+
+ try {
+ startReceiving ();
+ }
+ catch {
+ _services.Stop (1011, String.Empty);
+
+ throw;
+ }
+
+ _state = ServerState.Start;
+ }
+ }
+
+ private void startReceiving ()
+ {
+ try {
+ _listener.Start ();
+ }
+ catch (Exception ex) {
+ var msg = "The underlying listener has failed to start.";
+
+ throw new InvalidOperationException (msg, ex);
+ }
+
+ var receiver = new ThreadStart (receiveRequest);
+ _receiveThread = new Thread (receiver);
+ _receiveThread.IsBackground = true;
+
+ _receiveThread.Start ();
+ }
+
+ private void stop (ushort code, string reason)
+ {
+ lock (_sync) {
+ if (_state != ServerState.Start)
+ return;
+
+ _state = ServerState.ShuttingDown;
+ }
+
+ try {
+ _services.Stop (code, reason);
+ }
+ catch (Exception ex) {
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
+ }
+
+ try {
+ var timeout = 5000;
+
+ stopReceiving (timeout);
+ }
+ catch (Exception ex) {
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
+ }
+
+ _state = ServerState.Stop;
+ }
+
+ private void stopReceiving (int millisecondsTimeout)
+ {
+ _listener.Stop ();
+ _receiveThread.Join (millisecondsTimeout);
+ }
+
+ private static bool tryCreateUri (
+ string uriString,
+ out Uri result,
+ out string message
+ )
+ {
+ result = null;
+ message = null;
+
+ var uri = uriString.ToUri ();
+
+ if (uri == null) {
+ message = "An invalid URI string.";
+
+ return false;
+ }
+
+ if (!uri.IsAbsoluteUri) {
+ message = "A relative URI.";
+
+ return false;
+ }
+
+ var schm = uri.Scheme;
+ var isHttpSchm = schm == "http" || schm == "https";
+
+ if (!isHttpSchm) {
+ message = "The scheme part is not 'http' or 'https'.";
+
+ return false;
+ }
+
+ if (uri.PathAndQuery != "/") {
+ message = "It includes either or both path and query components.";
+
+ return false;
+ }
+
+ if (uri.Fragment.Length > 0) {
+ message = "It includes the fragment component.";
+
+ return false;
+ }
+
+ if (uri.Port == 0) {
+ message = "The port part is zero.";
+
+ return false;
+ }
+
+ result = uri;
+
+ return true;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Adds a WebSocket service with the specified behavior and path.
+ ///
+ ///
+ ///
+ /// A that specifies an absolute path to
+ /// the service to add.
+ ///
+ ///
+ /// / is trimmed from the end of the string if present.
+ ///
+ ///
+ ///
+ ///
+ /// The type of the behavior for the service.
+ ///
+ ///
+ /// It must inherit the class.
+ ///
+ ///
+ /// Also it must have a public parameterless constructor.
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is not an absolute path.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// includes either or both
+ /// query and fragment components.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is already in use.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public void AddWebSocketService (string path)
+ where TBehavior : WebSocketBehavior, new ()
+ {
+ _services.AddService (path, null);
+ }
+
+ ///
+ /// Adds a WebSocket service with the specified behavior, path,
+ /// and initializer.
+ ///
+ ///
+ ///
+ /// A that specifies an absolute path to
+ /// the service to add.
+ ///
+ ///
+ /// / is trimmed from the end of the string if present.
+ ///
+ ///
+ ///
+ ///
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the service initializes
+ /// a new session instance.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ ///
+ ///
+ /// The type of the behavior for the service.
+ ///
+ ///
+ /// It must inherit the class.
+ ///
+ ///
+ /// Also it must have a public parameterless constructor.
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is not an absolute path.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// includes either or both
+ /// query and fragment components.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is already in use.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public void AddWebSocketService (
+ string path,
+ Action initializer
+ )
+ where TBehavior : WebSocketBehavior, new ()
+ {
+ _services.AddService (path, initializer);
+ }
+
+ ///
+ /// Removes a WebSocket service with the specified path.
+ ///
+ ///
+ /// The service is stopped with close status 1001 (going away)
+ /// if the current state of the service is Start.
+ ///
+ ///
+ /// true if the service is successfully found and removed;
+ /// otherwise, false.
+ ///
+ ///
+ ///
+ /// A that specifies an absolute path to
+ /// the service to remove.
+ ///
+ ///
+ /// / is trimmed from the end of the string if present.
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is not an absolute path.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// includes either or both
+ /// query and fragment components.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public bool RemoveWebSocketService (string path)
+ {
+ return _services.RemoveService (path);
+ }
+
+ ///
+ /// Starts receiving incoming requests.
+ ///
+ ///
+ /// This method works if the current state of the server is Ready or Stop.
+ ///
+ ///
+ ///
+ /// There is no server certificate for secure connection.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The underlying has failed to start.
+ ///
+ ///
+ public void Start ()
+ {
+ if (_state == ServerState.Start || _state == ServerState.ShuttingDown)
+ return;
+
+ start ();
+ }
+
+ ///
+ /// Stops receiving incoming requests.
+ ///
+ ///
+ /// This method works if the current state of the server is Start.
+ ///
+ public void Stop ()
+ {
+ if (_state != ServerState.Start)
+ return;
+
+ stop (1001, String.Empty);
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Server/HttpServer.cs.meta b/Assets/External/websocket-sharp/Server/HttpServer.cs.meta
new file mode 100644
index 00000000..4689ca4e
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/HttpServer.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 8d339d9aa7e38394f843e684efdd9c21
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Server/IWebSocketSession.cs b/Assets/External/websocket-sharp/Server/IWebSocketSession.cs
new file mode 100644
index 00000000..4ddc84ac
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/IWebSocketSession.cs
@@ -0,0 +1,67 @@
+#region License
+/*
+ * IWebSocketSession.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2013-2022 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;
+
+namespace WebSocketSharp.Server
+{
+ ///
+ /// Exposes the access to the information in a WebSocket session.
+ ///
+ public interface IWebSocketSession
+ {
+ #region Properties
+
+ ///
+ /// Gets the unique ID of the session.
+ ///
+ ///
+ /// A that represents the unique ID of the session.
+ ///
+ string ID { get; }
+
+ ///
+ /// Gets the time that the session has started.
+ ///
+ ///
+ /// A that represents the time that the session
+ /// has started.
+ ///
+ DateTime StartTime { get; }
+
+ ///
+ /// Gets the WebSocket interface for the session.
+ ///
+ ///
+ /// A that represents the interface.
+ ///
+ WebSocket WebSocket { get; }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Server/IWebSocketSession.cs.meta b/Assets/External/websocket-sharp/Server/IWebSocketSession.cs.meta
new file mode 100644
index 00000000..9d20d581
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/IWebSocketSession.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 5b07fb165c9332243ab441eb20cbc943
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Server/ServerState.cs b/Assets/External/websocket-sharp/Server/ServerState.cs
new file mode 100644
index 00000000..2d758292
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/ServerState.cs
@@ -0,0 +1,40 @@
+#region License
+/*
+ * ServerState.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2013-2014 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;
+
+namespace WebSocketSharp.Server
+{
+ internal enum ServerState
+ {
+ Ready,
+ Start,
+ ShuttingDown,
+ Stop
+ }
+}
diff --git a/Assets/External/websocket-sharp/Server/ServerState.cs.meta b/Assets/External/websocket-sharp/Server/ServerState.cs.meta
new file mode 100644
index 00000000..6c1adef4
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/ServerState.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: f3824b70e1911674ab2224cdf519e5e2
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Server/WebSocketBehavior.cs b/Assets/External/websocket-sharp/Server/WebSocketBehavior.cs
new file mode 100644
index 00000000..b8375c8b
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/WebSocketBehavior.cs
@@ -0,0 +1,1570 @@
+#region License
+/*
+ * WebSocketBehavior.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2025 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.Specialized;
+using System.IO;
+using System.Security.Principal;
+using WebSocketSharp.Net;
+using WebSocketSharp.Net.WebSockets;
+
+namespace WebSocketSharp.Server
+{
+ ///
+ /// Exposes a set of methods and properties used to define the behavior of
+ /// a WebSocket service provided by the or
+ /// class.
+ ///
+ ///
+ /// This class is an abstract class.
+ ///
+ public abstract class WebSocketBehavior : IWebSocketSession
+ {
+ #region Private Fields
+
+ private WebSocketContext _context;
+ private Func _cookiesValidator;
+ private bool _emitOnPing;
+ private Func _hostValidator;
+ private string _id;
+ private bool _ignoreExtensions;
+ private bool _noDelay;
+ private Func _originValidator;
+ private string _protocol;
+ private WebSocketSessionManager _sessions;
+ private DateTime _startTime;
+ private WebSocket _websocket;
+
+ #endregion
+
+ #region Protected Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected WebSocketBehavior ()
+ {
+ _startTime = DateTime.MaxValue;
+ }
+
+ #endregion
+
+ #region Protected Properties
+
+ ///
+ /// Gets the HTTP headers for a session.
+ ///
+ ///
+ /// A that contains the headers
+ /// included in the WebSocket handshake request.
+ ///
+ ///
+ /// The get operation is not available when the session has not started yet.
+ ///
+ protected NameValueCollection Headers {
+ get {
+ if (_context == null) {
+ var msg = "The get operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _context.Headers;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the communication is possible for
+ /// a session.
+ ///
+ ///
+ /// true if the communication is possible; otherwise, false.
+ ///
+ ///
+ /// The get operation is not available when the session has not started yet.
+ ///
+ protected bool IsAlive {
+ get {
+ if (_websocket == null) {
+ var msg = "The get operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _websocket.IsAlive;
+ }
+ }
+
+ ///
+ /// Gets the query string for a session.
+ ///
+ ///
+ ///
+ /// A that contains the query
+ /// parameters included in the WebSocket handshake request.
+ ///
+ ///
+ /// An empty collection if not included.
+ ///
+ ///
+ ///
+ /// The get operation is not available when the session has not started yet.
+ ///
+ protected NameValueCollection QueryString {
+ get {
+ if (_context == null) {
+ var msg = "The get operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _context.QueryString;
+ }
+ }
+
+ ///
+ /// Gets the current state of the WebSocket interface for a session.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It indicates the current state of the interface.
+ ///
+ ///
+ ///
+ /// The get operation is not available when the session has not started yet.
+ ///
+ protected WebSocketState ReadyState {
+ get {
+ if (_websocket == null) {
+ var msg = "The get operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _websocket.ReadyState;
+ }
+ }
+
+ ///
+ /// Gets the management function for the sessions in the service.
+ ///
+ ///
+ /// A that manages the sessions in
+ /// the service.
+ ///
+ ///
+ /// The get operation is not available when the session has not started yet.
+ ///
+ protected WebSocketSessionManager Sessions {
+ get {
+ if (_sessions == null) {
+ var msg = "The get operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _sessions;
+ }
+ }
+
+ ///
+ /// Gets the client information for a session.
+ ///
+ ///
+ ///
+ /// A instance that represents identity,
+ /// authentication, and security roles for the client.
+ ///
+ ///
+ /// if the client is not authenticated.
+ ///
+ ///
+ ///
+ /// The get operation is not available when the session has not started yet.
+ ///
+ protected IPrincipal User {
+ get {
+ if (_context == null) {
+ var msg = "The get operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _context.User;
+ }
+ }
+
+ ///
+ /// Gets the client endpoint for a session.
+ ///
+ ///
+ /// A that represents the client
+ /// IP address and port number.
+ ///
+ ///
+ /// The get operation is not available when the session has not started yet.
+ ///
+ protected System.Net.IPEndPoint UserEndPoint {
+ get {
+ if (_context == null) {
+ var msg = "The get operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _context.UserEndPoint;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets or sets the delegate used to validate the HTTP cookies.
+ ///
+ ///
+ ///
+ /// A
+ /// delegate.
+ ///
+ ///
+ /// It represents the delegate called when the WebSocket interface
+ /// for a session validates the handshake request.
+ ///
+ ///
+ /// 1st parameter passed to the delegate
+ /// contains the cookies to validate.
+ ///
+ ///
+ /// 2nd parameter passed to the delegate
+ /// holds the cookies to send to the client.
+ ///
+ ///
+ /// The method invoked by the delegate must return true
+ /// if the cookies are valid.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ ///
+ /// The set operation is not available when the session has already started.
+ ///
+ public Func CookiesValidator {
+ get {
+ return _cookiesValidator;
+ }
+
+ set {
+ if (_websocket != null) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _cookiesValidator = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the WebSocket interface for
+ /// a session emits the message event when it receives a ping.
+ ///
+ ///
+ ///
+ /// true if the interface emits the message event when it receives
+ /// a ping; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ ///
+ /// The set operation is not available when the session has already started.
+ ///
+ public bool EmitOnPing {
+ get {
+ return _emitOnPing;
+ }
+
+ set {
+ if (_websocket != null) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _emitOnPing = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the delegate used to validate the Host header.
+ ///
+ ///
+ ///
+ /// A delegate.
+ ///
+ ///
+ /// It represents the delegate called when the WebSocket interface
+ /// for a session validates the handshake request.
+ ///
+ ///
+ /// The parameter passed to the delegate is
+ /// the value of the Host header.
+ ///
+ ///
+ /// The method invoked by the delegate must return true
+ /// if the header value is valid.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ ///
+ /// The set operation is not available when the session has already started.
+ ///
+ public Func HostValidator {
+ get {
+ return _hostValidator;
+ }
+
+ set {
+ if (_websocket != null) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _hostValidator = value;
+ }
+ }
+
+ ///
+ /// Gets the unique ID of a session.
+ ///
+ ///
+ ///
+ /// A that represents the unique ID of the session.
+ ///
+ ///
+ /// when the session has not started yet.
+ ///
+ ///
+ public string ID {
+ get {
+ return _id;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the WebSocket interface for
+ /// a session ignores the Sec-WebSocket-Extensions header.
+ ///
+ ///
+ ///
+ /// true if the interface ignores the extensions requested
+ /// from the client; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ ///
+ /// The set operation is not available when the session has already started.
+ ///
+ public bool IgnoreExtensions {
+ get {
+ return _ignoreExtensions;
+ }
+
+ set {
+ if (_websocket != null) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _ignoreExtensions = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the underlying TCP socket of
+ /// the WebSocket interface for a session disables a delay when send or
+ /// receive buffer is not full.
+ ///
+ ///
+ ///
+ /// true if the delay is disabled; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ ///
+ ///
+ /// The set operation is not available when the session has already started.
+ ///
+ public bool NoDelay {
+ get {
+ return _noDelay;
+ }
+
+ set {
+ if (_websocket != null) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _noDelay = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the delegate used to validate the Origin header.
+ ///
+ ///
+ ///
+ /// A delegate.
+ ///
+ ///
+ /// It represents the delegate called when the WebSocket interface
+ /// for a session validates the handshake request.
+ ///
+ ///
+ /// The parameter passed to the delegate is
+ /// the value of the Origin header or if
+ /// the header is not present.
+ ///
+ ///
+ /// The method invoked by the delegate must return true
+ /// if the header value is valid.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ ///
+ /// The set operation is not available when the session has already started.
+ ///
+ public Func OriginValidator {
+ get {
+ return _originValidator;
+ }
+
+ set {
+ if (_websocket != null) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _originValidator = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the name of the WebSocket subprotocol for a session.
+ ///
+ ///
+ ///
+ /// A that represents the name of the subprotocol.
+ ///
+ ///
+ /// The value specified for a set operation must be a token defined in
+ ///
+ /// RFC 2616.
+ ///
+ ///
+ /// The value is initialized if not requested.
+ ///
+ ///
+ /// The default value is an empty string.
+ ///
+ ///
+ ///
+ /// The value specified for a set operation is not a token.
+ ///
+ ///
+ /// The set operation is not available when the session has already started.
+ ///
+ public string Protocol {
+ get {
+ return _websocket != null
+ ? _websocket.Protocol
+ : (_protocol ?? String.Empty);
+ }
+
+ set {
+ if (_websocket != null) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (value == null || value.Length == 0) {
+ _protocol = null;
+
+ return;
+ }
+
+ if (!value.IsToken ()) {
+ var msg = "Not a token.";
+
+ throw new ArgumentException (msg, "value");
+ }
+
+ _protocol = value;
+ }
+ }
+
+ ///
+ /// Gets the time that a session has started.
+ ///
+ ///
+ ///
+ /// A that represents the time that the session
+ /// has started.
+ ///
+ ///
+ /// when the session has not started yet.
+ ///
+ ///
+ public DateTime StartTime {
+ get {
+ return _startTime;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private string checkHandshakeRequest (WebSocketContext context)
+ {
+ if (_hostValidator != null) {
+ if (!_hostValidator (context.Host)) {
+ var msg = "The Host header is invalid.";
+
+ return msg;
+ }
+ }
+
+ if (_originValidator != null) {
+ if (!_originValidator (context.Origin)) {
+ var msg = "The Origin header is non-existent or invalid.";
+
+ return msg;
+ }
+ }
+
+ if (_cookiesValidator != null) {
+ var req = context.CookieCollection;
+ var res = context.WebSocket.CookieCollection;
+
+ if (!_cookiesValidator (req, res)) {
+ var msg = "The Cookie header is non-existent or invalid.";
+
+ return msg;
+ }
+ }
+
+ return null;
+ }
+
+ private void onClose (object sender, CloseEventArgs e)
+ {
+ if (_id == null)
+ return;
+
+ _sessions.Remove (_id);
+
+ OnClose (e);
+ }
+
+ private void onError (object sender, ErrorEventArgs e)
+ {
+ OnError (e);
+ }
+
+ private void onMessage (object sender, MessageEventArgs e)
+ {
+ OnMessage (e);
+ }
+
+ private void onOpen (object sender, EventArgs e)
+ {
+ _id = _sessions.Add (this);
+
+ if (_id == null) {
+ _websocket.Close (CloseStatusCode.Away);
+
+ return;
+ }
+
+ _startTime = DateTime.Now;
+
+ OnOpen ();
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal void Start (
+ WebSocketContext context,
+ WebSocketSessionManager sessions
+ )
+ {
+ _context = context;
+ _sessions = sessions;
+
+ _websocket = context.WebSocket;
+ _websocket.CustomHandshakeRequestChecker = checkHandshakeRequest;
+
+ if (_emitOnPing)
+ _websocket.EmitOnPing = true;
+
+ if (_ignoreExtensions)
+ _websocket.IgnoreExtensions = true;
+
+ if (_noDelay)
+ _websocket.NoDelay = true;
+
+ if (_protocol != null)
+ _websocket.Protocol = _protocol;
+
+ var waitTime = sessions.WaitTime;
+
+ if (waitTime != _websocket.WaitTime)
+ _websocket.WaitTime = waitTime;
+
+ _websocket.OnClose += onClose;
+ _websocket.OnError += onError;
+ _websocket.OnMessage += onMessage;
+ _websocket.OnOpen += onOpen;
+
+ _websocket.Accept ();
+ }
+
+ #endregion
+
+ #region Protected Methods
+
+ ///
+ /// Closes the WebSocket connection for a session.
+ ///
+ ///
+ /// This method does nothing when the current state of the WebSocket
+ /// interface is Closing or Closed.
+ ///
+ ///
+ /// The Close method is not available when the session has not started yet.
+ ///
+ protected void Close ()
+ {
+ if (_websocket == null) {
+ var msg = "The Close method is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _websocket.Close ();
+ }
+
+ ///
+ /// Closes the WebSocket connection for a session with the specified
+ /// status code and reason.
+ ///
+ ///
+ /// This method does nothing when the current state of the WebSocket
+ /// interface is Closing or Closed.
+ ///
+ ///
+ ///
+ /// A that specifies the status code indicating
+ /// the reason for the close.
+ ///
+ ///
+ /// The status codes are defined in
+ ///
+ /// Section 7.4 of RFC 6455.
+ ///
+ ///
+ ///
+ ///
+ /// A that specifies the reason for the close.
+ ///
+ ///
+ /// Its size must be 123 bytes or less in UTF-8.
+ ///
+ ///
+ ///
+ ///
+ /// is 1010 (mandatory extension).
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is 1005 (no status) and
+ /// is specified.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ ///
+ ///
+ /// is less than 1000 or greater than 4999.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The size of is greater than 123 bytes.
+ ///
+ ///
+ ///
+ /// The Close method is not available when the session has not started yet.
+ ///
+ protected void Close (ushort code, string reason)
+ {
+ if (_websocket == null) {
+ var msg = "The Close method is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _websocket.Close (code, reason);
+ }
+
+ ///
+ /// Closes the WebSocket connection for a session with the specified
+ /// status code and reason.
+ ///
+ ///
+ /// This method does nothing when the current state of the WebSocket
+ /// interface is Closing or Closed.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It specifies the status code indicating the reason for the close.
+ ///
+ ///
+ ///
+ ///
+ /// A that specifies the reason for the close.
+ ///
+ ///
+ /// Its size must be 123 bytes or less in UTF-8.
+ ///
+ ///
+ ///
+ ///
+ /// is an undefined enum value.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is and
+ /// is specified.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ ///
+ /// The size of is greater than 123 bytes.
+ ///
+ ///
+ /// The Close method is not available when the session has not started yet.
+ ///
+ protected void Close (CloseStatusCode code, string reason)
+ {
+ if (_websocket == null) {
+ var msg = "The Close method is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _websocket.Close (code, reason);
+ }
+
+ ///
+ /// Closes the WebSocket connection for a session asynchronously.
+ ///
+ ///
+ ///
+ /// This method does not wait for the close to be complete.
+ ///
+ ///
+ /// This method does nothing when the current state of the WebSocket
+ /// interface is Closing or Closed.
+ ///
+ ///
+ ///
+ /// The CloseAsync method is not available when the session has not
+ /// started yet.
+ ///
+ protected void CloseAsync ()
+ {
+ if (_websocket == null) {
+ var msg = "The CloseAsync method is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _websocket.CloseAsync ();
+ }
+
+ ///
+ /// Closes the WebSocket connection for a session asynchronously with
+ /// the specified status code and reason.
+ ///
+ ///
+ ///
+ /// This method does not wait for the close to be complete.
+ ///
+ ///
+ /// This method does nothing when the current state of the WebSocket
+ /// interface is Closing or Closed.
+ ///
+ ///
+ ///
+ ///
+ /// A that specifies the status code indicating
+ /// the reason for the close.
+ ///
+ ///
+ /// The status codes are defined in
+ ///
+ /// Section 7.4 of RFC 6455.
+ ///
+ ///
+ ///
+ ///
+ /// A that specifies the reason for the close.
+ ///
+ ///
+ /// Its size must be 123 bytes or less in UTF-8.
+ ///
+ ///
+ ///
+ ///
+ /// is 1010 (mandatory extension).
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is 1005 (no status) and
+ /// is specified.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ ///
+ ///
+ /// is less than 1000 or greater than 4999.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The size of is greater than 123 bytes.
+ ///
+ ///
+ ///
+ /// The CloseAsync method is not available when the session has not
+ /// started yet.
+ ///
+ protected void CloseAsync (ushort code, string reason)
+ {
+ if (_websocket == null) {
+ var msg = "The CloseAsync method is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _websocket.CloseAsync (code, reason);
+ }
+
+ ///
+ /// Closes the WebSocket connection for a session asynchronously with
+ /// the specified status code and reason.
+ ///
+ ///
+ ///
+ /// This method does not wait for the close to be complete.
+ ///
+ ///
+ /// This method does nothing when the current state of the WebSocket
+ /// interface is Closing or Closed.
+ ///
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It specifies the status code indicating the reason for the close.
+ ///
+ ///
+ ///
+ ///
+ /// A that specifies the reason for the close.
+ ///
+ ///
+ /// Its size must be 123 bytes or less in UTF-8.
+ ///
+ ///
+ ///
+ ///
+ /// is an undefined enum value.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is and
+ /// is specified.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ ///
+ /// The size of is greater than 123 bytes.
+ ///
+ ///
+ /// The CloseAsync method is not available when the session has not
+ /// started yet.
+ ///
+ protected void CloseAsync (CloseStatusCode code, string reason)
+ {
+ if (_websocket == null) {
+ var msg = "The CloseAsync method is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _websocket.CloseAsync (code, reason);
+ }
+
+ ///
+ /// Called when the WebSocket connection for a session has been closed.
+ ///
+ ///
+ /// A that represents the event data passed
+ /// from a event.
+ ///
+ protected virtual void OnClose (CloseEventArgs e)
+ {
+ }
+
+ ///
+ /// Called when the WebSocket interface for a session gets an error.
+ ///
+ ///
+ /// A that represents the event data passed
+ /// from a event.
+ ///
+ protected virtual void OnError (ErrorEventArgs e)
+ {
+ }
+
+ ///
+ /// Called when the WebSocket interface for a session receives a message.
+ ///
+ ///
+ /// A that represents the event data passed
+ /// from a event.
+ ///
+ protected virtual void OnMessage (MessageEventArgs e)
+ {
+ }
+
+ ///
+ /// Called when the WebSocket connection for a session has been established.
+ ///
+ protected virtual void OnOpen ()
+ {
+ }
+
+ ///
+ /// Sends a ping to the client for a session.
+ ///
+ ///
+ /// true if the send has successfully done and a pong has been
+ /// received within a time; otherwise, false.
+ ///
+ ///
+ /// The Ping method is not available when the session has not started yet.
+ ///
+ protected bool Ping ()
+ {
+ if (_websocket == null) {
+ var msg = "The Ping method is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _websocket.Ping ();
+ }
+
+ ///
+ /// Sends a ping with the specified message to the client for a session.
+ ///
+ ///
+ /// true if the send has successfully done and a pong has been
+ /// received within a time; otherwise, false.
+ ///
+ ///
+ ///
+ /// A that specifies the message to send.
+ ///
+ ///
+ /// Its size must be 125 bytes or less in UTF-8.
+ ///
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ /// The size of is greater than 125 bytes.
+ ///
+ ///
+ /// The Ping method is not available when the session has not started yet.
+ ///
+ protected bool Ping (string message)
+ {
+ if (_websocket == null) {
+ var msg = "The Ping method is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _websocket.Ping (message);
+ }
+
+ ///
+ /// Sends the specified data to the client for a session.
+ ///
+ ///
+ /// An array of that specifies the binary data to send.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// The Send method is not available when the session has not
+ /// started yet.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The Send method is not available when the current state of
+ /// the WebSocket interface is not Open.
+ ///
+ ///
+ protected void Send (byte[] data)
+ {
+ if (_websocket == null) {
+ var msg = "The Send method is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _websocket.Send (data);
+ }
+
+ ///
+ /// Sends the specified file to the client for a session.
+ ///
+ ///
+ ///
+ /// A that specifies the file to send.
+ ///
+ ///
+ /// The file is sent as the binary data.
+ ///
+ ///
+ ///
+ ///
+ /// The file does not exist.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The file could not be opened.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// The Send method is not available when the session has not
+ /// started yet.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The Send method is not available when the current state of
+ /// the WebSocket interface is not Open.
+ ///
+ ///
+ protected void Send (FileInfo fileInfo)
+ {
+ if (_websocket == null) {
+ var msg = "The Send method is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _websocket.Send (fileInfo);
+ }
+
+ ///
+ /// Sends the specified data to the client for a session.
+ ///
+ ///
+ /// A that specifies the text data to send.
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// The Send method is not available when the session has not
+ /// started yet.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The Send method is not available when the current state of
+ /// the WebSocket interface is not Open.
+ ///
+ ///
+ protected void Send (string data)
+ {
+ if (_websocket == null) {
+ var msg = "The Send method is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _websocket.Send (data);
+ }
+
+ ///
+ /// Sends the data from the specified stream instance to the client for
+ /// a session.
+ ///
+ ///
+ ///
+ /// A instance from which to read the data to send.
+ ///
+ ///
+ /// The data is sent as the binary data.
+ ///
+ ///
+ ///
+ /// An that specifies the number of bytes to send.
+ ///
+ ///
+ ///
+ /// cannot be read.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is less than 1.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// No data could be read from .
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// The Send method is not available when the session has not
+ /// started yet.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The Send method is not available when the current state of
+ /// the WebSocket interface is not Open.
+ ///
+ ///
+ protected void Send (Stream stream, int length)
+ {
+ if (_websocket == null) {
+ var msg = "The Send method is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _websocket.Send (stream, length);
+ }
+
+ ///
+ /// Sends the specified data to the client for a session asynchronously.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ /// An array of that specifies the binary data to send.
+ ///
+ ///
+ ///
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the send is complete.
+ ///
+ ///
+ /// The parameter passed to the delegate is true
+ /// if the send has successfully done; otherwise, false.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// The SendAsync method is not available when the session has not
+ /// started yet.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The SendAsync method is not available when the current state of
+ /// the WebSocket interface is not Open.
+ ///
+ ///
+ protected void SendAsync (byte[] data, Action completed)
+ {
+ if (_websocket == null) {
+ var msg = "The SendAsync method is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _websocket.SendAsync (data, completed);
+ }
+
+ ///
+ /// Sends the specified file to the client for a session asynchronously.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ ///
+ /// A that specifies the file to send.
+ ///
+ ///
+ /// The file is sent as the binary data.
+ ///
+ ///
+ ///
+ ///
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the send is complete.
+ ///
+ ///
+ /// The parameter passed to the delegate is true
+ /// if the send has successfully done; otherwise, false.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ ///
+ ///
+ /// The file does not exist.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The file could not be opened.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// The SendAsync method is not available when the session has not
+ /// started yet.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The SendAsync method is not available when the current state of
+ /// the WebSocket interface is not Open.
+ ///
+ ///
+ protected void SendAsync (FileInfo fileInfo, Action completed)
+ {
+ if (_websocket == null) {
+ var msg = "The SendAsync method is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _websocket.SendAsync (fileInfo, completed);
+ }
+
+ ///
+ /// Sends the specified data to the client for a session asynchronously.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ /// A that specifies the text data to send.
+ ///
+ ///
+ ///
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the send is complete.
+ ///
+ ///
+ /// The parameter passed to the delegate is true
+ /// if the send has successfully done; otherwise, false.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// The SendAsync method is not available when the session has not
+ /// started yet.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The SendAsync method is not available when the current state of
+ /// the WebSocket interface is not Open.
+ ///
+ ///
+ protected void SendAsync (string data, Action completed)
+ {
+ if (_websocket == null) {
+ var msg = "The SendAsync method is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _websocket.SendAsync (data, completed);
+ }
+
+ ///
+ /// Sends the data from the specified stream instance to the client for
+ /// a session asynchronously.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ ///
+ /// A instance from which to read the data to send.
+ ///
+ ///
+ /// The data is sent as the binary data.
+ ///
+ ///
+ ///
+ /// An that specifies the number of bytes to send.
+ ///
+ ///
+ ///
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the send is complete.
+ ///
+ ///
+ /// The parameter passed to the delegate is true
+ /// if the send has successfully done; otherwise, false.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ ///
+ ///
+ /// cannot be read.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is less than 1.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// No data could be read from .
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// The SendAsync method is not available when the session has not
+ /// started yet.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The SendAsync method is not available when the current state of
+ /// the WebSocket interface is not Open.
+ ///
+ ///
+ protected void SendAsync (Stream stream, int length, Action completed)
+ {
+ if (_websocket == null) {
+ var msg = "The SendAsync method is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _websocket.SendAsync (stream, length, completed);
+ }
+
+ #endregion
+
+ #region Explicit Interface Implementations
+
+ ///
+ /// Gets the WebSocket interface for a session.
+ ///
+ ///
+ ///
+ /// A that represents
+ /// the WebSocket interface.
+ ///
+ ///
+ /// when the session has not started yet.
+ ///
+ ///
+ WebSocket IWebSocketSession.WebSocket {
+ get {
+ return _websocket;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Server/WebSocketBehavior.cs.meta b/Assets/External/websocket-sharp/Server/WebSocketBehavior.cs.meta
new file mode 100644
index 00000000..96ae0887
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/WebSocketBehavior.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: c7cce5577fefb5649a0ceddc552c5b1e
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Server/WebSocketServer.cs b/Assets/External/websocket-sharp/Server/WebSocketServer.cs
new file mode 100644
index 00000000..50f03f02
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/WebSocketServer.cs
@@ -0,0 +1,1185 @@
+#region License
+/*
+ * WebSocketServer.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2024 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
+
+#region Contributors
+/*
+ * Contributors:
+ * - Juan Manuel Lallana
+ * - Jonas Hovgaard
+ * - Liryna
+ * - Rohan Singh
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Net.Sockets;
+using System.Security.Cryptography.X509Certificates;
+using System.Security.Principal;
+using System.Text;
+using System.Threading;
+using WebSocketSharp.Net;
+using WebSocketSharp.Net.WebSockets;
+
+namespace WebSocketSharp.Server
+{
+ ///
+ /// Provides a WebSocket protocol server.
+ ///
+ ///
+ /// This class can provide multiple WebSocket services.
+ ///
+ public class WebSocketServer
+ {
+ #region Private Fields
+
+ private System.Net.IPAddress _address;
+ private AuthenticationSchemes _authSchemes;
+ private static readonly string _defaultRealm;
+ private string _hostname;
+ private bool _isDnsStyle;
+ private bool _isSecure;
+ private TcpListener _listener;
+ private Logger _log;
+ private int _port;
+ private string _realm;
+ private string _realmInUse;
+ private Thread _receiveThread;
+ private bool _reuseAddress;
+ private WebSocketServiceManager _services;
+ private ServerSslConfiguration _sslConfig;
+ private ServerSslConfiguration _sslConfigInUse;
+ private volatile ServerState _state;
+ private object _sync;
+ private Func _userCredFinder;
+
+ #endregion
+
+ #region Static Constructor
+
+ static WebSocketServer ()
+ {
+ _defaultRealm = "SECRET AREA";
+ }
+
+ #endregion
+
+ #region Public Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The new instance listens for incoming handshake requests on
+ /// and port 80.
+ ///
+ public WebSocketServer ()
+ {
+ var addr = System.Net.IPAddress.Any;
+
+ init (addr.ToString (), addr, 80, false);
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// with the specified port.
+ ///
+ ///
+ ///
+ /// The new instance listens for incoming handshake requests on
+ /// and .
+ ///
+ ///
+ /// It provides secure connections if is 443.
+ ///
+ ///
+ ///
+ /// An that specifies the number of the port on which
+ /// to listen.
+ ///
+ ///
+ /// is less than 1 or greater than 65535.
+ ///
+ public WebSocketServer (int port)
+ : this (port, port == 443)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// with the specified URL.
+ ///
+ ///
+ ///
+ /// The new instance listens for incoming handshake requests on
+ /// the IP address and port of .
+ ///
+ ///
+ /// Either port 80 or 443 is used if includes
+ /// no port. Port 443 is used if the scheme of
+ /// is wss; otherwise, port 80 is used.
+ ///
+ ///
+ /// The new instance provides secure connections if the scheme of
+ /// is wss.
+ ///
+ ///
+ ///
+ /// A that specifies the WebSocket URL of the server.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is invalid.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public WebSocketServer (string url)
+ {
+ if (url == null)
+ throw new ArgumentNullException ("url");
+
+ if (url.Length == 0)
+ throw new ArgumentException ("An empty string.", "url");
+
+ Uri uri;
+ string msg;
+
+ if (!tryCreateUri (url, out uri, out msg))
+ throw new ArgumentException (msg, "url");
+
+ var host = uri.DnsSafeHost;
+ var addr = host.ToIPAddress ();
+
+ if (addr == null) {
+ msg = "The host part could not be converted to an IP address.";
+
+ throw new ArgumentException (msg, "url");
+ }
+
+ if (!addr.IsLocal ()) {
+ msg = "The IP address of the host is not a local IP address.";
+
+ throw new ArgumentException (msg, "url");
+ }
+
+ init (host, addr, uri.Port, uri.Scheme == "wss");
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// with the specified port and boolean if secure or not.
+ ///
+ ///
+ /// The new instance listens for incoming handshake requests on
+ /// and .
+ ///
+ ///
+ /// An that specifies the number of the port on which
+ /// to listen.
+ ///
+ ///
+ /// A : true if the new instance provides
+ /// secure connections; otherwise, false.
+ ///
+ ///
+ /// is less than 1 or greater than 65535.
+ ///
+ public WebSocketServer (int port, bool secure)
+ {
+ if (!port.IsPortNumber ()) {
+ var msg = "Less than 1 or greater than 65535.";
+
+ throw new ArgumentOutOfRangeException ("port", msg);
+ }
+
+ var addr = System.Net.IPAddress.Any;
+
+ init (addr.ToString (), addr, port, secure);
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// with the specified IP address and port.
+ ///
+ ///
+ ///
+ /// The new instance listens for incoming handshake requests on
+ /// and .
+ ///
+ ///
+ /// It provides secure connections if is 443.
+ ///
+ ///
+ ///
+ /// A that specifies the local IP
+ /// address on which to listen.
+ ///
+ ///
+ /// An that specifies the number of the port on which
+ /// to listen.
+ ///
+ ///
+ /// is not a local IP address.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// is less than 1 or greater than 65535.
+ ///
+ public WebSocketServer (System.Net.IPAddress address, int port)
+ : this (address, port, port == 443)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// with the specified IP address, port, and boolean if secure or not.
+ ///
+ ///
+ /// The new instance listens for incoming handshake requests on
+ /// and .
+ ///
+ ///
+ /// A that specifies the local IP
+ /// address on which to listen.
+ ///
+ ///
+ /// An that specifies the number of the port on which
+ /// to listen.
+ ///
+ ///
+ /// A : true if the new instance provides
+ /// secure connections; otherwise, false.
+ ///
+ ///
+ /// is not a local IP address.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// is less than 1 or greater than 65535.
+ ///
+ public WebSocketServer (System.Net.IPAddress address, int port, bool secure)
+ {
+ if (address == null)
+ throw new ArgumentNullException ("address");
+
+ if (!address.IsLocal ()) {
+ var msg = "Not a local IP address.";
+
+ throw new ArgumentException (msg, "address");
+ }
+
+ if (!port.IsPortNumber ()) {
+ var msg = "Less than 1 or greater than 65535.";
+
+ throw new ArgumentOutOfRangeException ("port", msg);
+ }
+
+ init (address.ToString (), address, port, secure);
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the IP address of the server.
+ ///
+ ///
+ /// A that represents the local IP
+ /// address on which to listen for incoming handshake requests.
+ ///
+ public System.Net.IPAddress Address {
+ get {
+ return _address;
+ }
+ }
+
+ ///
+ /// Gets or sets the scheme used to authenticate the clients.
+ ///
+ ///
+ /// The set operation works if the current state of the server is
+ /// Ready or Stop.
+ ///
+ ///
+ ///
+ /// One of the
+ /// enum values.
+ ///
+ ///
+ /// It represents the scheme used to authenticate the clients.
+ ///
+ ///
+ /// The default value is
+ /// .
+ ///
+ ///
+ public AuthenticationSchemes AuthenticationSchemes {
+ get {
+ return _authSchemes;
+ }
+
+ set {
+ lock (_sync) {
+ if (!canSet ())
+ return;
+
+ _authSchemes = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the server has started.
+ ///
+ ///
+ /// true if the server has started; otherwise, false.
+ ///
+ public bool IsListening {
+ get {
+ return _state == ServerState.Start;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the server provides secure connections.
+ ///
+ ///
+ /// true if the server provides secure connections; otherwise,
+ /// false.
+ ///
+ public bool IsSecure {
+ get {
+ return _isSecure;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the server cleans up
+ /// the inactive sessions periodically.
+ ///
+ ///
+ /// The set operation works if the current state of the server is
+ /// Ready or Stop.
+ ///
+ ///
+ ///
+ /// true if the server cleans up the inactive sessions
+ /// every 60 seconds; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ public bool KeepClean {
+ get {
+ return _services.KeepClean;
+ }
+
+ set {
+ _services.KeepClean = value;
+ }
+ }
+
+ ///
+ /// Gets the logging function for the server.
+ ///
+ ///
+ /// The default logging level is .
+ ///
+ ///
+ /// A that provides the logging function.
+ ///
+ public Logger Log {
+ get {
+ return _log;
+ }
+ }
+
+ ///
+ /// Gets the port of the server.
+ ///
+ ///
+ /// An that represents the number of the port on which
+ /// to listen for incoming handshake requests.
+ ///
+ public int Port {
+ get {
+ return _port;
+ }
+ }
+
+ ///
+ /// Gets or sets the name of the realm associated with the server.
+ ///
+ ///
+ /// The set operation works if the current state of the server is
+ /// Ready or Stop.
+ ///
+ ///
+ ///
+ /// A that represents the name of the realm.
+ ///
+ ///
+ /// "SECRET AREA" is used as the name of the realm if the value is
+ /// or an empty string.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ public string Realm {
+ get {
+ return _realm;
+ }
+
+ set {
+ lock (_sync) {
+ if (!canSet ())
+ return;
+
+ _realm = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the server is allowed to
+ /// be bound to an address that is already in use.
+ ///
+ ///
+ ///
+ /// You should set this property to true if you would like to
+ /// resolve to wait for socket in TIME_WAIT state.
+ ///
+ ///
+ /// The set operation works if the current state of the server is
+ /// Ready or Stop.
+ ///
+ ///
+ ///
+ ///
+ /// true if the server is allowed to be bound to an address
+ /// that is already in use; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ public bool ReuseAddress {
+ get {
+ return _reuseAddress;
+ }
+
+ set {
+ lock (_sync) {
+ if (!canSet ())
+ return;
+
+ _reuseAddress = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets the configuration for secure connection.
+ ///
+ ///
+ /// The configuration is used when the server attempts to start,
+ /// so it must be configured before the start method is called.
+ ///
+ ///
+ /// A that represents the
+ /// configuration used to provide secure connections.
+ ///
+ ///
+ /// The server does not provide secure connections.
+ ///
+ public ServerSslConfiguration SslConfiguration {
+ get {
+ if (!_isSecure) {
+ var msg = "The server does not provide secure connections.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return getSslConfiguration ();
+ }
+ }
+
+ ///
+ /// Gets or sets the delegate called to find the credentials for
+ /// an identity used to authenticate a client.
+ ///
+ ///
+ /// The set operation works if the current state of the server is
+ /// Ready or Stop.
+ ///
+ ///
+ ///
+ /// A
+ /// delegate.
+ ///
+ ///
+ /// It represents the delegate called when the server finds
+ /// the credentials used to authenticate a client.
+ ///
+ ///
+ /// It must return if the credentials
+ /// are not found.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ public Func UserCredentialsFinder {
+ get {
+ return _userCredFinder;
+ }
+
+ set {
+ lock (_sync) {
+ if (!canSet ())
+ return;
+
+ _userCredFinder = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the time to wait for the response to the WebSocket
+ /// Ping or Close.
+ ///
+ ///
+ /// The set operation works if the current state of the server is
+ /// Ready or Stop.
+ ///
+ ///
+ ///
+ /// A that represents the time to wait for
+ /// the response.
+ ///
+ ///
+ /// The default value is the same as 1 second.
+ ///
+ ///
+ ///
+ /// The value specified for a set operation is zero or less.
+ ///
+ public TimeSpan WaitTime {
+ get {
+ return _services.WaitTime;
+ }
+
+ set {
+ _services.WaitTime = value;
+ }
+ }
+
+ ///
+ /// Gets the management function for the WebSocket services provided by
+ /// the server.
+ ///
+ ///
+ /// A that manages the WebSocket
+ /// services provided by the server.
+ ///
+ public WebSocketServiceManager WebSocketServices {
+ get {
+ return _services;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private void abort ()
+ {
+ lock (_sync) {
+ if (_state != ServerState.Start)
+ return;
+
+ _state = ServerState.ShuttingDown;
+ }
+
+ try {
+ _listener.Stop ();
+ }
+ catch (Exception ex) {
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
+ }
+
+ try {
+ _services.Stop (1006, String.Empty);
+ }
+ catch (Exception ex) {
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
+ }
+
+ _state = ServerState.Stop;
+ }
+
+ private bool authenticateClient (TcpListenerWebSocketContext context)
+ {
+ if (_authSchemes == AuthenticationSchemes.Anonymous)
+ return true;
+
+ if (_authSchemes == AuthenticationSchemes.None)
+ return false;
+
+ var chal = new AuthenticationChallenge (_authSchemes, _realmInUse)
+ .ToString ();
+
+ var retry = -1;
+ Func auth = null;
+ auth =
+ () => {
+ retry++;
+
+ if (retry > 99)
+ return false;
+
+ if (context.SetUser (_authSchemes, _realmInUse, _userCredFinder))
+ return true;
+
+ context.SendAuthenticationChallenge (chal);
+
+ return auth ();
+ };
+
+ return auth ();
+ }
+
+ private bool canSet ()
+ {
+ return _state == ServerState.Ready || _state == ServerState.Stop;
+ }
+
+ private bool checkHostNameForRequest (string name)
+ {
+ return !_isDnsStyle
+ || Uri.CheckHostName (name) != UriHostNameType.Dns
+ || name == _hostname;
+ }
+
+ private string getRealm ()
+ {
+ var realm = _realm;
+
+ return realm != null && realm.Length > 0 ? realm : _defaultRealm;
+ }
+
+ private ServerSslConfiguration getSslConfiguration ()
+ {
+ if (_sslConfig == null)
+ _sslConfig = new ServerSslConfiguration ();
+
+ return _sslConfig;
+ }
+
+ private void init (
+ string hostname,
+ System.Net.IPAddress address,
+ int port,
+ bool secure
+ )
+ {
+ _hostname = hostname;
+ _address = address;
+ _port = port;
+ _isSecure = secure;
+
+ _authSchemes = AuthenticationSchemes.Anonymous;
+ _isDnsStyle = Uri.CheckHostName (hostname) == UriHostNameType.Dns;
+ _listener = new TcpListener (address, port);
+ _log = new Logger ();
+ _services = new WebSocketServiceManager (_log);
+ _sync = new object ();
+ }
+
+ private void processRequest (TcpListenerWebSocketContext context)
+ {
+ if (!authenticateClient (context)) {
+ context.Close (HttpStatusCode.Forbidden);
+
+ return;
+ }
+
+ var uri = context.RequestUri;
+
+ if (uri == null) {
+ context.Close (HttpStatusCode.BadRequest);
+
+ return;
+ }
+
+ var name = uri.DnsSafeHost;
+
+ if (!checkHostNameForRequest (name)) {
+ context.Close (HttpStatusCode.NotFound);
+
+ return;
+ }
+
+ var path = uri.AbsolutePath;
+
+ if (path.IndexOfAny (new[] { '%', '+' }) > -1)
+ path = HttpUtility.UrlDecode (path, Encoding.UTF8);
+
+ WebSocketServiceHost host;
+
+ if (!_services.InternalTryGetServiceHost (path, out host)) {
+ context.Close (HttpStatusCode.NotImplemented);
+
+ return;
+ }
+
+ host.StartSession (context);
+ }
+
+ private void receiveRequest ()
+ {
+ while (true) {
+ TcpClient cl = null;
+
+ try {
+ cl = _listener.AcceptTcpClient ();
+
+ ThreadPool.QueueUserWorkItem (
+ state => {
+ try {
+ var ctx = new TcpListenerWebSocketContext (
+ cl,
+ null,
+ _isSecure,
+ _sslConfigInUse,
+ _log
+ );
+
+ processRequest (ctx);
+ }
+ catch (Exception ex) {
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ cl.Close ();
+ }
+ }
+ );
+ }
+ catch (SocketException ex) {
+ if (_state == ServerState.ShuttingDown)
+ return;
+
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ break;
+ }
+ catch (InvalidOperationException ex) {
+ if (_state == ServerState.ShuttingDown)
+ return;
+
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ break;
+ }
+ catch (Exception ex) {
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ if (cl != null)
+ cl.Close ();
+
+ if (_state == ServerState.ShuttingDown)
+ return;
+
+ break;
+ }
+ }
+
+ abort ();
+ }
+
+ private void start ()
+ {
+ lock (_sync) {
+ if (_state == ServerState.Start || _state == ServerState.ShuttingDown)
+ return;
+
+ if (_isSecure) {
+ var src = getSslConfiguration ();
+ var conf = new ServerSslConfiguration (src);
+
+ if (conf.ServerCertificate == null) {
+ var msg = "There is no server certificate for secure connection.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _sslConfigInUse = conf;
+ }
+
+ _realmInUse = getRealm ();
+
+ _services.Start ();
+
+ try {
+ startReceiving ();
+ }
+ catch {
+ _services.Stop (1011, String.Empty);
+
+ throw;
+ }
+
+ _state = ServerState.Start;
+ }
+ }
+
+ private void startReceiving ()
+ {
+ if (_reuseAddress) {
+ _listener.Server.SetSocketOption (
+ SocketOptionLevel.Socket,
+ SocketOptionName.ReuseAddress,
+ true
+ );
+ }
+
+ try {
+ _listener.Start ();
+ }
+ catch (Exception ex) {
+ var msg = "The underlying listener has failed to start.";
+
+ throw new InvalidOperationException (msg, ex);
+ }
+
+ var receiver = new ThreadStart (receiveRequest);
+ _receiveThread = new Thread (receiver);
+ _receiveThread.IsBackground = true;
+
+ _receiveThread.Start ();
+ }
+
+ private void stop (ushort code, string reason)
+ {
+ lock (_sync) {
+ if (_state != ServerState.Start)
+ return;
+
+ _state = ServerState.ShuttingDown;
+ }
+
+ try {
+ var timeout = 5000;
+
+ stopReceiving (timeout);
+ }
+ catch (Exception ex) {
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
+ }
+
+ try {
+ _services.Stop (code, reason);
+ }
+ catch (Exception ex) {
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
+ }
+
+ _state = ServerState.Stop;
+ }
+
+ private void stopReceiving (int millisecondsTimeout)
+ {
+ _listener.Stop ();
+ _receiveThread.Join (millisecondsTimeout);
+ }
+
+ private static bool tryCreateUri (
+ string uriString,
+ out Uri result,
+ out string message
+ )
+ {
+ if (!uriString.TryCreateWebSocketUri (out result, out message))
+ return false;
+
+ if (result.PathAndQuery != "/") {
+ result = null;
+ message = "It includes either or both path and query components.";
+
+ return false;
+ }
+
+ return true;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Adds a WebSocket service with the specified behavior and path.
+ ///
+ ///
+ ///
+ /// A that specifies an absolute path to
+ /// the service to add.
+ ///
+ ///
+ /// / is trimmed from the end of the string if present.
+ ///
+ ///
+ ///
+ ///
+ /// The type of the behavior for the service.
+ ///
+ ///
+ /// It must inherit the class.
+ ///
+ ///
+ /// Also it must have a public parameterless constructor.
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is not an absolute path.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// includes either or both
+ /// query and fragment components.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is already in use.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public void AddWebSocketService (string path)
+ where TBehavior : WebSocketBehavior, new ()
+ {
+ _services.AddService (path, null);
+ }
+
+ ///
+ /// Adds a WebSocket service with the specified behavior, path,
+ /// and initializer.
+ ///
+ ///
+ ///
+ /// A that specifies an absolute path to
+ /// the service to add.
+ ///
+ ///
+ /// / is trimmed from the end of the string if present.
+ ///
+ ///
+ ///
+ ///
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the service initializes
+ /// a new session instance.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ ///
+ ///
+ /// The type of the behavior for the service.
+ ///
+ ///
+ /// It must inherit the class.
+ ///
+ ///
+ /// Also it must have a public parameterless constructor.
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is not an absolute path.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// includes either or both
+ /// query and fragment components.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is already in use.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public void AddWebSocketService (
+ string path,
+ Action initializer
+ )
+ where TBehavior : WebSocketBehavior, new ()
+ {
+ _services.AddService (path, initializer);
+ }
+
+ ///
+ /// Removes a WebSocket service with the specified path.
+ ///
+ ///
+ /// The service is stopped with close status 1001 (going away)
+ /// if the current state of the service is Start.
+ ///
+ ///
+ /// true if the service is successfully found and removed;
+ /// otherwise, false.
+ ///
+ ///
+ ///
+ /// A that specifies an absolute path to
+ /// the service to remove.
+ ///
+ ///
+ /// / is trimmed from the end of the string if present.
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is not an absolute path.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// includes either or both
+ /// query and fragment components.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public bool RemoveWebSocketService (string path)
+ {
+ return _services.RemoveService (path);
+ }
+
+ ///
+ /// Starts receiving incoming handshake requests.
+ ///
+ ///
+ /// This method works if the current state of the server is Ready or Stop.
+ ///
+ ///
+ ///
+ /// There is no server certificate for secure connection.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The underlying has failed to start.
+ ///
+ ///
+ public void Start ()
+ {
+ if (_state == ServerState.Start || _state == ServerState.ShuttingDown)
+ return;
+
+ start ();
+ }
+
+ ///
+ /// Stops receiving incoming handshake requests.
+ ///
+ ///
+ /// This method works if the current state of the server is Start.
+ ///
+ public void Stop ()
+ {
+ if (_state != ServerState.Start)
+ return;
+
+ stop (1001, String.Empty);
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Server/WebSocketServer.cs.meta b/Assets/External/websocket-sharp/Server/WebSocketServer.cs.meta
new file mode 100644
index 00000000..b3ad2911
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/WebSocketServer.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 49525996bcadedf4fa4e6f2d9e20a97e
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Server/WebSocketServiceHost.cs b/Assets/External/websocket-sharp/Server/WebSocketServiceHost.cs
new file mode 100644
index 00000000..ee20d92a
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/WebSocketServiceHost.cs
@@ -0,0 +1,227 @@
+#region License
+/*
+ * WebSocketServiceHost.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2023 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
+
+#region Contributors
+/*
+ * Contributors:
+ * - Juan Manuel Lallana
+ */
+#endregion
+
+using System;
+using WebSocketSharp.Net.WebSockets;
+
+namespace WebSocketSharp.Server
+{
+ ///
+ /// Exposes the methods and properties used to access the information in
+ /// a WebSocket service provided by the or
+ /// class.
+ ///
+ ///
+ /// This class is an abstract class.
+ ///
+ public abstract class WebSocketServiceHost
+ {
+ #region Private Fields
+
+ private Logger _log;
+ private string _path;
+ private WebSocketSessionManager _sessions;
+
+ #endregion
+
+ #region Protected Constructors
+
+ ///
+ /// Initializes a new instance of the
+ /// class with the specified path and logging function.
+ ///
+ ///
+ /// A that specifies the absolute path to
+ /// the service.
+ ///
+ ///
+ /// A that specifies the logging function for
+ /// the service.
+ ///
+ protected WebSocketServiceHost (string path, Logger log)
+ {
+ _path = path;
+ _log = log;
+
+ _sessions = new WebSocketSessionManager (log);
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal ServerState State {
+ get {
+ return _sessions.State;
+ }
+ }
+
+ #endregion
+
+ #region Protected Properties
+
+ ///
+ /// Gets the logging function for the service.
+ ///
+ ///
+ /// A that provides the logging function.
+ ///
+ protected Logger Log {
+ get {
+ return _log;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets or sets a value indicating whether the service cleans up
+ /// the inactive sessions periodically.
+ ///
+ ///
+ /// The set operation works if the current state of the service is
+ /// Ready or Stop.
+ ///
+ ///
+ /// true if the service cleans up the inactive sessions every
+ /// 60 seconds; otherwise, false.
+ ///
+ public bool KeepClean {
+ get {
+ return _sessions.KeepClean;
+ }
+
+ set {
+ _sessions.KeepClean = value;
+ }
+ }
+
+ ///
+ /// Gets the path to the service.
+ ///
+ ///
+ /// A that represents the absolute path to
+ /// the service.
+ ///
+ public string Path {
+ get {
+ return _path;
+ }
+ }
+
+ ///
+ /// Gets the management function for the sessions in the service.
+ ///
+ ///
+ /// A that manages the sessions in
+ /// the service.
+ ///
+ public WebSocketSessionManager Sessions {
+ get {
+ return _sessions;
+ }
+ }
+
+ ///
+ /// Gets the type of the behavior of the service.
+ ///
+ ///
+ /// A that represents the type of the behavior of
+ /// the service.
+ ///
+ public abstract Type BehaviorType { get; }
+
+ ///
+ /// Gets or sets the time to wait for the response to the WebSocket
+ /// Ping or Close.
+ ///
+ ///
+ /// The set operation works if the current state of the service is
+ /// Ready or Stop.
+ ///
+ ///
+ /// A that represents the time to wait for
+ /// the response.
+ ///
+ ///
+ /// The value specified for a set operation is zero or less.
+ ///
+ public TimeSpan WaitTime {
+ get {
+ return _sessions.WaitTime;
+ }
+
+ set {
+ _sessions.WaitTime = value;
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal void Start ()
+ {
+ _sessions.Start ();
+ }
+
+ internal void StartSession (WebSocketContext context)
+ {
+ CreateSession ().Start (context, _sessions);
+ }
+
+ internal void Stop (ushort code, string reason)
+ {
+ _sessions.Stop (code, reason);
+ }
+
+ #endregion
+
+ #region Protected Methods
+
+ ///
+ /// Creates a new session for the service.
+ ///
+ ///
+ /// A instance that represents
+ /// the new session.
+ ///
+ protected abstract WebSocketBehavior CreateSession ();
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Server/WebSocketServiceHost.cs.meta b/Assets/External/websocket-sharp/Server/WebSocketServiceHost.cs.meta
new file mode 100644
index 00000000..c6a57d68
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/WebSocketServiceHost.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: c23b3e16ef5430845954233291ba2fca
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Server/WebSocketServiceHost`1.cs b/Assets/External/websocket-sharp/Server/WebSocketServiceHost`1.cs
new file mode 100644
index 00000000..8aac424e
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/WebSocketServiceHost`1.cs
@@ -0,0 +1,95 @@
+#region License
+/*
+ * WebSocketServiceHost`1.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2015-2024 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;
+
+namespace WebSocketSharp.Server
+{
+ internal class WebSocketServiceHost : WebSocketServiceHost
+ where TBehavior : WebSocketBehavior, new ()
+ {
+ #region Private Fields
+
+ private Func _creator;
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal WebSocketServiceHost (
+ string path,
+ Action initializer,
+ Logger log
+ )
+ : base (path, log)
+ {
+ _creator = createSessionCreator (initializer);
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ public override Type BehaviorType {
+ get {
+ return typeof (TBehavior);
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private static Func createSessionCreator (
+ Action initializer
+ )
+ {
+ if (initializer == null)
+ return () => new TBehavior ();
+
+ return () => {
+ var ret = new TBehavior ();
+
+ initializer (ret);
+
+ return ret;
+ };
+ }
+
+ #endregion
+
+ #region Protected Methods
+
+ protected override WebSocketBehavior CreateSession ()
+ {
+ return _creator ();
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Server/WebSocketServiceHost`1.cs.meta b/Assets/External/websocket-sharp/Server/WebSocketServiceHost`1.cs.meta
new file mode 100644
index 00000000..777c0961
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/WebSocketServiceHost`1.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: e0aafad565049ac4d96a16ce24b080e9
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Server/WebSocketServiceManager.cs b/Assets/External/websocket-sharp/Server/WebSocketServiceManager.cs
new file mode 100644
index 00000000..29021062
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/WebSocketServiceManager.cs
@@ -0,0 +1,614 @@
+#region License
+/*
+ * WebSocketServiceManager.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2024 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;
+using System.Collections.Generic;
+
+namespace WebSocketSharp.Server
+{
+ ///
+ /// Provides the management function for the WebSocket services.
+ ///
+ ///
+ /// This class manages the WebSocket services provided by the
+ /// or class.
+ ///
+ public class WebSocketServiceManager
+ {
+ #region Private Fields
+
+ private Dictionary _hosts;
+ private volatile bool _keepClean;
+ private Logger _log;
+ private volatile ServerState _state;
+ private object _sync;
+ private TimeSpan _waitTime;
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal WebSocketServiceManager (Logger log)
+ {
+ _log = log;
+
+ _hosts = new Dictionary ();
+ _state = ServerState.Ready;
+ _sync = ((ICollection) _hosts).SyncRoot;
+ _waitTime = TimeSpan.FromSeconds (1);
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the number of the WebSocket services.
+ ///
+ ///
+ /// An that represents the number of the services.
+ ///
+ public int Count {
+ get {
+ lock (_sync)
+ return _hosts.Count;
+ }
+ }
+
+ ///
+ /// Gets the service host instances for the WebSocket services.
+ ///
+ ///
+ ///
+ /// An
+ /// instance.
+ ///
+ ///
+ /// It provides an enumerator which supports the iteration over
+ /// the collection of the service host instances.
+ ///
+ ///
+ public IEnumerable Hosts {
+ get {
+ lock (_sync)
+ return _hosts.Values.ToList ();
+ }
+ }
+
+ ///
+ /// Gets the service host instance for a WebSocket service with
+ /// the specified path.
+ ///
+ ///
+ ///
+ /// A instance that represents
+ /// the service host instance.
+ ///
+ ///
+ /// It provides the function to access the information in the service.
+ ///
+ ///
+ /// if not found.
+ ///
+ ///
+ ///
+ ///
+ /// A that specifies an absolute path to
+ /// the service to get.
+ ///
+ ///
+ /// / is trimmed from the end of the string if present.
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is not an absolute path.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// includes either or both
+ /// query and fragment components.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public WebSocketServiceHost this[string path] {
+ get {
+ if (path == null)
+ throw new ArgumentNullException ("path");
+
+ if (path.Length == 0)
+ throw new ArgumentException ("An empty string.", "path");
+
+ if (path[0] != '/') {
+ var msg = "Not an absolute path.";
+
+ throw new ArgumentException (msg, "path");
+ }
+
+ if (path.IndexOfAny (new[] { '?', '#' }) > -1) {
+ var msg = "It includes either or both query and fragment components.";
+
+ throw new ArgumentException (msg, "path");
+ }
+
+ WebSocketServiceHost host;
+
+ InternalTryGetServiceHost (path, out host);
+
+ return host;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the inactive sessions in
+ /// the WebSocket services are cleaned up periodically.
+ ///
+ ///
+ /// The set operation works if the current state of the server is
+ /// Ready or Stop.
+ ///
+ ///
+ ///
+ /// true if the inactive sessions are cleaned up every 60
+ /// seconds; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ public bool KeepClean {
+ get {
+ return _keepClean;
+ }
+
+ set {
+ lock (_sync) {
+ if (!canSet ())
+ return;
+
+ foreach (var host in _hosts.Values)
+ host.KeepClean = value;
+
+ _keepClean = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets the paths for the WebSocket services.
+ ///
+ ///
+ ///
+ /// An
+ /// instance.
+ ///
+ ///
+ /// It provides an enumerator which supports the iteration over
+ /// the collection of the paths.
+ ///
+ ///
+ public IEnumerable Paths {
+ get {
+ lock (_sync)
+ return _hosts.Keys.ToList ();
+ }
+ }
+
+ ///
+ /// Gets or sets the time to wait for the response to the WebSocket
+ /// Ping or Close.
+ ///
+ ///
+ /// The set operation works if the current state of the server is
+ /// Ready or Stop.
+ ///
+ ///
+ ///
+ /// A that represents the time to wait for
+ /// the response.
+ ///
+ ///
+ /// The default value is the same as 1 second.
+ ///
+ ///
+ ///
+ /// The value specified for a set operation is zero or less.
+ ///
+ public TimeSpan WaitTime {
+ get {
+ return _waitTime;
+ }
+
+ set {
+ if (value <= TimeSpan.Zero) {
+ var msg = "Zero or less.";
+
+ throw new ArgumentOutOfRangeException ("value", msg);
+ }
+
+ lock (_sync) {
+ if (!canSet ())
+ return;
+
+ foreach (var host in _hosts.Values)
+ host.WaitTime = value;
+
+ _waitTime = value;
+ }
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private bool canSet ()
+ {
+ return _state == ServerState.Ready || _state == ServerState.Stop;
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal bool InternalTryGetServiceHost (
+ string path,
+ out WebSocketServiceHost host
+ )
+ {
+ path = path.TrimSlashFromEnd ();
+
+ lock (_sync)
+ return _hosts.TryGetValue (path, out host);
+ }
+
+ internal void Start ()
+ {
+ lock (_sync) {
+ foreach (var host in _hosts.Values)
+ host.Start ();
+
+ _state = ServerState.Start;
+ }
+ }
+
+ internal void Stop (ushort code, string reason)
+ {
+ lock (_sync) {
+ _state = ServerState.ShuttingDown;
+
+ foreach (var host in _hosts.Values)
+ host.Stop (code, reason);
+
+ _state = ServerState.Stop;
+ }
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Adds a WebSocket service with the specified behavior, path,
+ /// and initializer.
+ ///
+ ///
+ ///
+ /// A that specifies an absolute path to
+ /// the service to add.
+ ///
+ ///
+ /// / is trimmed from the end of the string if present.
+ ///
+ ///
+ ///
+ ///
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the service initializes
+ /// a new session instance.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ ///
+ ///
+ /// The type of the behavior for the service.
+ ///
+ ///
+ /// It must inherit the class.
+ ///
+ ///
+ /// Also it must have a public parameterless constructor.
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is not an absolute path.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// includes either or both
+ /// query and fragment components.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is already in use.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public void AddService (
+ string path,
+ Action initializer
+ )
+ where TBehavior : WebSocketBehavior, new ()
+ {
+ if (path == null)
+ throw new ArgumentNullException ("path");
+
+ if (path.Length == 0)
+ throw new ArgumentException ("An empty string.", "path");
+
+ if (path[0] != '/') {
+ var msg = "Not an absolute path.";
+
+ throw new ArgumentException (msg, "path");
+ }
+
+ if (path.IndexOfAny (new[] { '?', '#' }) > -1) {
+ var msg = "It includes either or both query and fragment components.";
+
+ throw new ArgumentException (msg, "path");
+ }
+
+ path = path.TrimSlashFromEnd ();
+
+ lock (_sync) {
+ WebSocketServiceHost host;
+
+ if (_hosts.TryGetValue (path, out host)) {
+ var msg = "It is already in use.";
+
+ throw new ArgumentException (msg, "path");
+ }
+
+ host = new WebSocketServiceHost (path, initializer, _log);
+
+ if (_keepClean)
+ host.KeepClean = true;
+
+ if (_waitTime != host.WaitTime)
+ host.WaitTime = _waitTime;
+
+ if (_state == ServerState.Start)
+ host.Start ();
+
+ _hosts.Add (path, host);
+ }
+ }
+
+ ///
+ /// Removes all WebSocket services managed by the manager.
+ ///
+ ///
+ /// Each service is stopped with close status 1001 (going away)
+ /// if the current state of the service is Start.
+ ///
+ public void Clear ()
+ {
+ List hosts = null;
+
+ lock (_sync) {
+ hosts = _hosts.Values.ToList ();
+
+ _hosts.Clear ();
+ }
+
+ foreach (var host in hosts) {
+ if (host.State == ServerState.Start)
+ host.Stop (1001, String.Empty);
+ }
+ }
+
+ ///
+ /// Removes a WebSocket service with the specified path.
+ ///
+ ///
+ /// The service is stopped with close status 1001 (going away)
+ /// if the current state of the service is Start.
+ ///
+ ///
+ /// true if the service is successfully found and removed;
+ /// otherwise, false.
+ ///
+ ///
+ ///
+ /// A that specifies an absolute path to
+ /// the service to remove.
+ ///
+ ///
+ /// / is trimmed from the end of the string if present.
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is not an absolute path.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// includes either or both
+ /// query and fragment components.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public bool RemoveService (string path)
+ {
+ if (path == null)
+ throw new ArgumentNullException ("path");
+
+ if (path.Length == 0)
+ throw new ArgumentException ("An empty string.", "path");
+
+ if (path[0] != '/') {
+ var msg = "Not an absolute path.";
+
+ throw new ArgumentException (msg, "path");
+ }
+
+ if (path.IndexOfAny (new[] { '?', '#' }) > -1) {
+ var msg = "It includes either or both query and fragment components.";
+
+ throw new ArgumentException (msg, "path");
+ }
+
+ path = path.TrimSlashFromEnd ();
+ WebSocketServiceHost host;
+
+ lock (_sync) {
+ if (!_hosts.TryGetValue (path, out host))
+ return false;
+
+ _hosts.Remove (path);
+ }
+
+ if (host.State == ServerState.Start)
+ host.Stop (1001, String.Empty);
+
+ return true;
+ }
+
+ ///
+ /// Tries to get the service host instance for a WebSocket service with
+ /// the specified path.
+ ///
+ ///
+ /// true if the try has succeeded; otherwise, false.
+ ///
+ ///
+ ///
+ /// A that specifies an absolute path to
+ /// the service to get.
+ ///
+ ///
+ /// / is trimmed from the end of the string if present.
+ ///
+ ///
+ ///
+ ///
+ /// When this method returns, a
+ /// instance that receives the service host instance.
+ ///
+ ///
+ /// It provides the function to access the information in the service.
+ ///
+ ///
+ /// if not found.
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is not an absolute path.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// includes either or both
+ /// query and fragment components.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public bool TryGetServiceHost (string path, out WebSocketServiceHost host)
+ {
+ if (path == null)
+ throw new ArgumentNullException ("path");
+
+ if (path.Length == 0)
+ throw new ArgumentException ("An empty string.", "path");
+
+ if (path[0] != '/') {
+ var msg = "Not an absolute path.";
+
+ throw new ArgumentException (msg, "path");
+ }
+
+ if (path.IndexOfAny (new[] { '?', '#' }) > -1) {
+ var msg = "It includes either or both query and fragment components.";
+
+ throw new ArgumentException (msg, "path");
+ }
+
+ return InternalTryGetServiceHost (path, out host);
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Server/WebSocketServiceManager.cs.meta b/Assets/External/websocket-sharp/Server/WebSocketServiceManager.cs.meta
new file mode 100644
index 00000000..81f73156
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/WebSocketServiceManager.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 40c8453c75273b241844a2b11ebfb1e0
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/Server/WebSocketSessionManager.cs b/Assets/External/websocket-sharp/Server/WebSocketSessionManager.cs
new file mode 100644
index 00000000..43072fcf
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/WebSocketSessionManager.cs
@@ -0,0 +1,1641 @@
+#region License
+/*
+ * WebSocketSessionManager.cs
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2012-2025 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;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Timers;
+
+namespace WebSocketSharp.Server
+{
+ ///
+ /// Provides the management function for the sessions in a WebSocket service.
+ ///
+ ///
+ /// This class manages the sessions in a WebSocket service provided by the
+ /// or class.
+ ///
+ public class WebSocketSessionManager
+ {
+ #region Private Fields
+
+ private object _forSweep;
+ private volatile bool _keepClean;
+ private Logger _log;
+ private static readonly byte[] _rawEmptyPingFrame;
+ private Dictionary _sessions;
+ private volatile ServerState _state;
+ private volatile bool _sweeping;
+ private System.Timers.Timer _sweepTimer;
+ private object _sync;
+ private TimeSpan _waitTime;
+
+ #endregion
+
+ #region Static Constructor
+
+ static WebSocketSessionManager ()
+ {
+ _rawEmptyPingFrame = WebSocketFrame.CreatePingFrame (false).ToArray ();
+ }
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal WebSocketSessionManager (Logger log)
+ {
+ _log = log;
+
+ _forSweep = new object ();
+ _sessions = new Dictionary ();
+ _state = ServerState.Ready;
+ _sync = ((ICollection) _sessions).SyncRoot;
+ _waitTime = TimeSpan.FromSeconds (1);
+
+ setSweepTimer (60000);
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal ServerState State {
+ get {
+ return _state;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the IDs for the active sessions in the WebSocket service.
+ ///
+ ///
+ ///
+ /// An
+ /// instance.
+ ///
+ ///
+ /// It provides an enumerator which supports the iteration over
+ /// the collection of the IDs for the active sessions.
+ ///
+ ///
+ public IEnumerable ActiveIDs {
+ get {
+ foreach (var res in broadping (_rawEmptyPingFrame)) {
+ if (res.Value)
+ yield return res.Key;
+ }
+ }
+ }
+
+ ///
+ /// Gets the number of the sessions in the WebSocket service.
+ ///
+ ///
+ /// An that represents the number of the sessions.
+ ///
+ public int Count {
+ get {
+ lock (_sync)
+ return _sessions.Count;
+ }
+ }
+
+ ///
+ /// Gets the IDs for the sessions in the WebSocket service.
+ ///
+ ///
+ ///
+ /// An
+ /// instance.
+ ///
+ ///
+ /// It provides an enumerator which supports the iteration over
+ /// the collection of the IDs for the sessions.
+ ///
+ ///
+ public IEnumerable IDs {
+ get {
+ if (_state != ServerState.Start)
+ return Enumerable.Empty ();
+
+ lock (_sync) {
+ if (_state != ServerState.Start)
+ return Enumerable.Empty ();
+
+ return _sessions.Keys.ToList ();
+ }
+ }
+ }
+
+ ///
+ /// Gets the IDs for the inactive sessions in the WebSocket service.
+ ///
+ ///
+ ///
+ /// An
+ /// instance.
+ ///
+ ///
+ /// It provides an enumerator which supports the iteration over
+ /// the collection of the IDs for the inactive sessions.
+ ///
+ ///
+ public IEnumerable InactiveIDs {
+ get {
+ foreach (var res in broadping (_rawEmptyPingFrame)) {
+ if (!res.Value)
+ yield return res.Key;
+ }
+ }
+ }
+
+ ///
+ /// Gets the session instance with the specified ID.
+ ///
+ ///
+ ///
+ /// A instance that provides
+ /// the function to access the information in the session.
+ ///
+ ///
+ /// if not found.
+ ///
+ ///
+ ///
+ /// A that specifies the ID of the session to get.
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// is .
+ ///
+ public IWebSocketSession this[string id] {
+ get {
+ if (id == null)
+ throw new ArgumentNullException ("id");
+
+ if (id.Length == 0)
+ throw new ArgumentException ("An empty string.", "id");
+
+ IWebSocketSession session;
+
+ tryGetSession (id, out session);
+
+ return session;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the inactive sessions in
+ /// the WebSocket service are cleaned up periodically.
+ ///
+ ///
+ /// The set operation works if the current state of the service is
+ /// Ready or Stop.
+ ///
+ ///
+ /// true if the inactive sessions are cleaned up every 60 seconds;
+ /// otherwise, false.
+ ///
+ public bool KeepClean {
+ get {
+ return _keepClean;
+ }
+
+ set {
+ lock (_sync) {
+ if (!canSet ())
+ return;
+
+ _keepClean = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets the session instances in the WebSocket service.
+ ///
+ ///
+ ///
+ /// An
+ /// instance.
+ ///
+ ///
+ /// It provides an enumerator which supports the iteration over
+ /// the collection of the session instances.
+ ///
+ ///
+ public IEnumerable Sessions {
+ get {
+ if (_state != ServerState.Start)
+ return Enumerable.Empty ();
+
+ lock (_sync) {
+ if (_state != ServerState.Start)
+ return Enumerable.Empty ();
+
+ return _sessions.Values.ToList ();
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the time to wait for the response to the WebSocket
+ /// Ping or Close.
+ ///
+ ///
+ /// The set operation works if the current state of the service is
+ /// Ready or Stop.
+ ///
+ ///
+ /// A that represents the time to wait for
+ /// the response.
+ ///
+ ///
+ /// The value specified for a set operation is zero or less.
+ ///
+ public TimeSpan WaitTime {
+ get {
+ return _waitTime;
+ }
+
+ set {
+ if (value <= TimeSpan.Zero) {
+ var msg = "Zero or less.";
+
+ throw new ArgumentOutOfRangeException ("value", msg);
+ }
+
+ lock (_sync) {
+ if (!canSet ())
+ return;
+
+ _waitTime = value;
+ }
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private void broadcast (Opcode opcode, byte[] data, Action completed)
+ {
+ var cache = new Dictionary ();
+
+ try {
+ foreach (var session in Sessions) {
+ if (_state != ServerState.Start) {
+ _log.Error ("The send is cancelled.");
+
+ break;
+ }
+
+ session.WebSocket.Send (opcode, data, cache);
+ }
+
+ if (completed != null)
+ completed ();
+ }
+ catch (Exception ex) {
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
+ }
+ finally {
+ cache.Clear ();
+ }
+ }
+
+ private void broadcast (
+ Opcode opcode,
+ Stream sourceStream,
+ Action completed
+ )
+ {
+ var cache = new Dictionary ();
+
+ try {
+ foreach (var session in Sessions) {
+ if (_state != ServerState.Start) {
+ _log.Error ("The send is cancelled.");
+
+ break;
+ }
+
+ session.WebSocket.Send (opcode, sourceStream, cache);
+ }
+
+ if (completed != null)
+ completed ();
+ }
+ catch (Exception ex) {
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
+ }
+ finally {
+ foreach (var cached in cache.Values)
+ cached.Dispose ();
+
+ cache.Clear ();
+ }
+ }
+
+ private void broadcastAsync (Opcode opcode, byte[] data, Action completed)
+ {
+ ThreadPool.QueueUserWorkItem (
+ state => broadcast (opcode, data, completed)
+ );
+ }
+
+ private void broadcastAsync (
+ Opcode opcode,
+ Stream sourceStream,
+ Action completed
+ )
+ {
+ ThreadPool.QueueUserWorkItem (
+ state => broadcast (opcode, sourceStream, completed)
+ );
+ }
+
+ private Dictionary broadping (byte[] rawFrame)
+ {
+ var ret = new Dictionary ();
+
+ foreach (var session in Sessions) {
+ if (_state != ServerState.Start) {
+ ret.Clear ();
+
+ break;
+ }
+
+ var res = session.WebSocket.Ping (rawFrame);
+
+ ret.Add (session.ID, res);
+ }
+
+ return ret;
+ }
+
+ private bool canSet ()
+ {
+ return _state == ServerState.Ready || _state == ServerState.Stop;
+ }
+
+ private static string createID ()
+ {
+ return Guid.NewGuid ().ToString ("N");
+ }
+
+ private void setSweepTimer (double interval)
+ {
+ _sweepTimer = new System.Timers.Timer (interval);
+ _sweepTimer.Elapsed += (sender, e) => Sweep ();
+ }
+
+ private void stop (PayloadData payloadData, bool send)
+ {
+ var rawFrame = send
+ ? WebSocketFrame
+ .CreateCloseFrame (payloadData, false)
+ .ToArray ()
+ : null;
+
+ lock (_sync) {
+ _state = ServerState.ShuttingDown;
+ _sweepTimer.Enabled = false;
+
+ foreach (var session in _sessions.Values.ToList ())
+ session.WebSocket.Close (payloadData, rawFrame);
+
+ _state = ServerState.Stop;
+ }
+ }
+
+ private bool tryGetSession (string id, out IWebSocketSession session)
+ {
+ session = null;
+
+ if (_state != ServerState.Start)
+ return false;
+
+ lock (_sync) {
+ if (_state != ServerState.Start)
+ return false;
+
+ return _sessions.TryGetValue (id, out session);
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal string Add (IWebSocketSession session)
+ {
+ lock (_sync) {
+ if (_state != ServerState.Start)
+ return null;
+
+ var id = createID ();
+
+ _sessions.Add (id, session);
+
+ return id;
+ }
+ }
+
+ internal bool Remove (string id)
+ {
+ lock (_sync)
+ return _sessions.Remove (id);
+ }
+
+ internal void Start ()
+ {
+ lock (_sync) {
+ _sweepTimer.Enabled = _keepClean;
+ _state = ServerState.Start;
+ }
+ }
+
+ internal void Stop (ushort code, string reason)
+ {
+ if (code == 1005) {
+ stop (PayloadData.Empty, true);
+
+ return;
+ }
+
+ var payloadData = new PayloadData (code, reason);
+ var send = !code.IsReservedStatusCode ();
+
+ stop (payloadData, send);
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Sends the specified data to every client in the WebSocket service.
+ ///
+ ///
+ /// An array of that specifies the binary data to send.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The current state of the service is not Start.
+ ///
+ public void Broadcast (byte[] data)
+ {
+ if (_state != ServerState.Start) {
+ var msg = "The current state of the service is not Start.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (data == null)
+ throw new ArgumentNullException ("data");
+
+ if (data.LongLength <= WebSocket.FragmentLength)
+ broadcast (Opcode.Binary, data, null);
+ else
+ broadcast (Opcode.Binary, new MemoryStream (data), null);
+ }
+
+ ///
+ /// Sends the specified data to every client in the WebSocket service.
+ ///
+ ///
+ /// A that specifies the text data to send.
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The current state of the service is not Start.
+ ///
+ public void Broadcast (string data)
+ {
+ if (_state != ServerState.Start) {
+ var msg = "The current state of the service is not Start.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (data == null)
+ throw new ArgumentNullException ("data");
+
+ byte[] bytes;
+
+ if (!data.TryGetUTF8EncodedBytes (out bytes)) {
+ var msg = "It could not be UTF-8-encoded.";
+
+ throw new ArgumentException (msg, "data");
+ }
+
+ if (bytes.LongLength <= WebSocket.FragmentLength)
+ broadcast (Opcode.Text, bytes, null);
+ else
+ broadcast (Opcode.Text, new MemoryStream (bytes), null);
+ }
+
+ ///
+ /// Sends the data from the specified stream instance to every client in
+ /// the WebSocket service.
+ ///
+ ///
+ ///
+ /// A instance from which to read the data to send.
+ ///
+ ///
+ /// The data is sent as the binary data.
+ ///
+ ///
+ ///
+ /// An that specifies the number of bytes to send.
+ ///
+ ///
+ ///
+ /// cannot be read.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is less than 1.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// No data could be read from .
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The current state of the service is not Start.
+ ///
+ public void Broadcast (Stream stream, int length)
+ {
+ if (_state != ServerState.Start) {
+ var msg = "The current state of the service is not Start.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (stream == null)
+ throw new ArgumentNullException ("stream");
+
+ if (!stream.CanRead) {
+ var msg = "It cannot be read.";
+
+ throw new ArgumentException (msg, "stream");
+ }
+
+ if (length < 1) {
+ var msg = "Less than 1.";
+
+ throw new ArgumentException (msg, "length");
+ }
+
+ var bytes = stream.ReadBytes (length);
+ var len = bytes.Length;
+
+ if (len == 0) {
+ var msg = "No data could be read from it.";
+
+ throw new ArgumentException (msg, "stream");
+ }
+
+ if (len < length) {
+ var fmt = "Only {0} byte(s) of data could be read from the stream.";
+ var msg = String.Format (fmt, len);
+
+ _log.Warn (msg);
+ }
+
+ if (len <= WebSocket.FragmentLength)
+ broadcast (Opcode.Binary, bytes, null);
+ else
+ broadcast (Opcode.Binary, new MemoryStream (bytes), null);
+ }
+
+ ///
+ /// Sends the specified data to every client in the WebSocket service
+ /// asynchronously.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ /// An array of that specifies the binary data to send.
+ ///
+ ///
+ ///
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the send is complete.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The current state of the service is not Start.
+ ///
+ public void BroadcastAsync (byte[] data, Action completed)
+ {
+ if (_state != ServerState.Start) {
+ var msg = "The current state of the service is not Start.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (data == null)
+ throw new ArgumentNullException ("data");
+
+ if (data.LongLength <= WebSocket.FragmentLength)
+ broadcastAsync (Opcode.Binary, data, completed);
+ else
+ broadcastAsync (Opcode.Binary, new MemoryStream (data), completed);
+ }
+
+ ///
+ /// Sends the specified data to every client in the WebSocket service
+ /// asynchronously.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ /// A that specifies the text data to send.
+ ///
+ ///
+ ///
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the send is complete.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The current state of the service is not Start.
+ ///
+ public void BroadcastAsync (string data, Action completed)
+ {
+ if (_state != ServerState.Start) {
+ var msg = "The current state of the service is not Start.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (data == null)
+ throw new ArgumentNullException ("data");
+
+ byte[] bytes;
+
+ if (!data.TryGetUTF8EncodedBytes (out bytes)) {
+ var msg = "It could not be UTF-8-encoded.";
+
+ throw new ArgumentException (msg, "data");
+ }
+
+ if (bytes.LongLength <= WebSocket.FragmentLength)
+ broadcastAsync (Opcode.Text, bytes, completed);
+ else
+ broadcastAsync (Opcode.Text, new MemoryStream (bytes), completed);
+ }
+
+ ///
+ /// Sends the data from the specified stream instance to every client in
+ /// the WebSocket service asynchronously.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ ///
+ /// A instance from which to read the data to send.
+ ///
+ ///
+ /// The data is sent as the binary data.
+ ///
+ ///
+ ///
+ /// An that specifies the number of bytes to send.
+ ///
+ ///
+ ///
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the send is complete.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ ///
+ ///
+ /// cannot be read.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is less than 1.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// No data could be read from .
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The current state of the service is not Start.
+ ///
+ public void BroadcastAsync (Stream stream, int length, Action completed)
+ {
+ if (_state != ServerState.Start) {
+ var msg = "The current state of the service is not Start.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (stream == null)
+ throw new ArgumentNullException ("stream");
+
+ if (!stream.CanRead) {
+ var msg = "It cannot be read.";
+
+ throw new ArgumentException (msg, "stream");
+ }
+
+ if (length < 1) {
+ var msg = "Less than 1.";
+
+ throw new ArgumentException (msg, "length");
+ }
+
+ var bytes = stream.ReadBytes (length);
+ var len = bytes.Length;
+
+ if (len == 0) {
+ var msg = "No data could be read from it.";
+
+ throw new ArgumentException (msg, "stream");
+ }
+
+ if (len < length) {
+ var fmt = "Only {0} byte(s) of data could be read from the stream.";
+ var msg = String.Format (fmt, len);
+
+ _log.Warn (msg);
+ }
+
+ if (len <= WebSocket.FragmentLength)
+ broadcastAsync (Opcode.Binary, bytes, completed);
+ else
+ broadcastAsync (Opcode.Binary, new MemoryStream (bytes), completed);
+ }
+
+ ///
+ /// Closes the session with the specified ID.
+ ///
+ ///
+ /// A that specifies the ID of the session to close.
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ public void CloseSession (string id)
+ {
+ IWebSocketSession session;
+
+ if (!TryGetSession (id, out session)) {
+ var msg = "The session could not be found.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ session.WebSocket.Close ();
+ }
+
+ ///
+ /// Closes the session with the specified ID, status code, and reason.
+ ///
+ ///
+ /// A that specifies the ID of the session to close.
+ ///
+ ///
+ ///
+ /// A that specifies the status code indicating
+ /// the reason for the close.
+ ///
+ ///
+ /// The status codes are defined in
+ ///
+ /// Section 7.4 of RFC 6455.
+ ///
+ ///
+ ///
+ ///
+ /// A that specifies the reason for the close.
+ ///
+ ///
+ /// Its size must be 123 bytes or less in UTF-8.
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is 1010 (mandatory extension).
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is 1005 (no status) and
+ /// is specified.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// is less than 1000 or greater than 4999.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The size of is greater than 123 bytes.
+ ///
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ public void CloseSession (string id, ushort code, string reason)
+ {
+ IWebSocketSession session;
+
+ if (!TryGetSession (id, out session)) {
+ var msg = "The session could not be found.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ session.WebSocket.Close (code, reason);
+ }
+
+ ///
+ /// Closes the session with the specified ID, status code, and reason.
+ ///
+ ///
+ /// A that specifies the ID of the session to close.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It specifies the status code indicating the reason for the close.
+ ///
+ ///
+ ///
+ ///
+ /// A that specifies the reason for the close.
+ ///
+ ///
+ /// Its size must be 123 bytes or less in UTF-8.
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is an undefined enum value.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is and
+ /// is specified.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The size of is greater than 123 bytes.
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ public void CloseSession (string id, CloseStatusCode code, string reason)
+ {
+ IWebSocketSession session;
+
+ if (!TryGetSession (id, out session)) {
+ var msg = "The session could not be found.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ session.WebSocket.Close (code, reason);
+ }
+
+ ///
+ /// Sends a ping to the client using the specified session.
+ ///
+ ///
+ /// true if the send has successfully done and a pong has been
+ /// received within a time; otherwise, false.
+ ///
+ ///
+ /// A that specifies the ID of the session.
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ public bool PingTo (string id)
+ {
+ IWebSocketSession session;
+
+ if (!TryGetSession (id, out session)) {
+ var msg = "The session could not be found.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return session.WebSocket.Ping ();
+ }
+
+ ///
+ /// Sends a ping with the specified message to the client using
+ /// the specified session.
+ ///
+ ///
+ /// true if the send has successfully done and a pong has been
+ /// received within a time; otherwise, false.
+ ///
+ ///
+ ///
+ /// A that specifies the message to send.
+ ///
+ ///
+ /// Its size must be 125 bytes or less in UTF-8.
+ ///
+ ///
+ ///
+ /// A that specifies the ID of the session.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The size of is greater than 125 bytes.
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ public bool PingTo (string message, string id)
+ {
+ IWebSocketSession session;
+
+ if (!TryGetSession (id, out session)) {
+ var msg = "The session could not be found.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return session.WebSocket.Ping (message);
+ }
+
+ ///
+ /// Sends the specified data to the client using the specified session.
+ ///
+ ///
+ /// An array of that specifies the binary data to send.
+ ///
+ ///
+ /// A that specifies the ID of the session.
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The current state of the WebSocket interface is not Open.
+ ///
+ ///
+ public void SendTo (byte[] data, string id)
+ {
+ IWebSocketSession session;
+
+ if (!TryGetSession (id, out session)) {
+ var msg = "The session could not be found.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ session.WebSocket.Send (data);
+ }
+
+ ///
+ /// Sends the specified data to the client using the specified session.
+ ///
+ ///
+ /// A that specifies the text data to send.
+ ///
+ ///
+ /// A that specifies the ID of the session.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The current state of the WebSocket interface is not Open.
+ ///
+ ///
+ public void SendTo (string data, string id)
+ {
+ IWebSocketSession session;
+
+ if (!TryGetSession (id, out session)) {
+ var msg = "The session could not be found.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ session.WebSocket.Send (data);
+ }
+
+ ///
+ /// Sends the data from the specified stream instance to the client using
+ /// the specified session.
+ ///
+ ///
+ ///
+ /// A instance from which to read the data to send.
+ ///
+ ///
+ /// The data is sent as the binary data.
+ ///
+ ///
+ ///
+ /// An that specifies the number of bytes to send.
+ ///
+ ///
+ /// A that specifies the ID of the session.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// cannot be read.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is less than 1.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// No data could be read from .
+ ///
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The current state of the WebSocket interface is not Open.
+ ///
+ ///
+ public void SendTo (Stream stream, int length, string id)
+ {
+ IWebSocketSession session;
+
+ if (!TryGetSession (id, out session)) {
+ var msg = "The session could not be found.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ session.WebSocket.Send (stream, length);
+ }
+
+ ///
+ /// Sends the specified data to the client using the specified session
+ /// asynchronously.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ /// An array of that specifies the binary data to send.
+ ///
+ ///
+ /// A that specifies the ID of the session.
+ ///
+ ///
+ ///
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the send is complete.
+ ///
+ ///
+ /// The parameter passed to the delegate is
+ /// true if the send has successfully done; otherwise,
+ /// false.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The current state of the WebSocket interface is not Open.
+ ///
+ ///
+ public void SendToAsync (byte[] data, string id, Action completed)
+ {
+ IWebSocketSession session;
+
+ if (!TryGetSession (id, out session)) {
+ var msg = "The session could not be found.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ session.WebSocket.SendAsync (data, completed);
+ }
+
+ ///
+ /// Sends the specified data to the client using the specified session
+ /// asynchronously.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ /// A that specifies the text data to send.
+ ///
+ ///
+ /// A that specifies the ID of the session.
+ ///
+ ///
+ ///
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the send is complete.
+ ///
+ ///
+ /// The parameter passed to the delegate is
+ /// true if the send has successfully done; otherwise,
+ /// false.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The current state of the WebSocket interface is not Open.
+ ///
+ ///
+ public void SendToAsync (string data, string id, Action completed)
+ {
+ IWebSocketSession session;
+
+ if (!TryGetSession (id, out session)) {
+ var msg = "The session could not be found.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ session.WebSocket.SendAsync (data, completed);
+ }
+
+ ///
+ /// Sends the data from the specified stream instance to the client using
+ /// the specified session asynchronously.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ ///
+ /// A instance from which to read the data to send.
+ ///
+ ///
+ /// The data is sent as the binary data.
+ ///
+ ///
+ ///
+ /// An that specifies the number of bytes to send.
+ ///
+ ///
+ /// A that specifies the ID of the session.
+ ///
+ ///
+ ///
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the send is complete.
+ ///
+ ///
+ /// The parameter passed to the delegate is
+ /// true if the send has successfully done; otherwise,
+ /// false.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// cannot be read.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is less than 1.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// No data could be read from .
+ ///
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The current state of the WebSocket interface is not Open.
+ ///
+ ///
+ public void SendToAsync (
+ Stream stream,
+ int length,
+ string id,
+ Action completed
+ )
+ {
+ IWebSocketSession session;
+
+ if (!TryGetSession (id, out session)) {
+ var msg = "The session could not be found.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ session.WebSocket.SendAsync (stream, length, completed);
+ }
+
+ ///
+ /// Cleans up the inactive sessions in the WebSocket service.
+ ///
+ public void Sweep ()
+ {
+ if (_sweeping) {
+ _log.Trace ("The sweep process is already in progress.");
+
+ return;
+ }
+
+ lock (_forSweep) {
+ if (_sweeping) {
+ _log.Trace ("The sweep process is already in progress.");
+
+ return;
+ }
+
+ _sweeping = true;
+ }
+
+ foreach (var id in InactiveIDs) {
+ if (_state != ServerState.Start)
+ break;
+
+ lock (_sync) {
+ if (_state != ServerState.Start)
+ break;
+
+ IWebSocketSession session;
+
+ if (!_sessions.TryGetValue (id, out session))
+ continue;
+
+ var state = session.WebSocket.ReadyState;
+
+ if (state == WebSocketState.Open) {
+ session.WebSocket.Close (CloseStatusCode.Abnormal);
+
+ continue;
+ }
+
+ if (state == WebSocketState.Closing)
+ continue;
+
+ _sessions.Remove (id);
+ }
+ }
+
+ lock (_forSweep)
+ _sweeping = false;
+ }
+
+ ///
+ /// Tries to get the session instance with the specified ID.
+ ///
+ ///
+ /// true if the try has succeeded; otherwise, false.
+ ///
+ ///
+ /// A that specifies the ID of the session to get.
+ ///
+ ///
+ ///
+ /// When this method returns, a instance
+ /// that receives the session instance.
+ ///
+ ///
+ /// It provides the function to access the information in the session.
+ ///
+ ///
+ /// if not found.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// is .
+ ///
+ public bool TryGetSession (string id, out IWebSocketSession session)
+ {
+ if (id == null)
+ throw new ArgumentNullException ("id");
+
+ if (id.Length == 0)
+ throw new ArgumentException ("An empty string.", "id");
+
+ return tryGetSession (id, out session);
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/External/websocket-sharp/Server/WebSocketSessionManager.cs.meta b/Assets/External/websocket-sharp/Server/WebSocketSessionManager.cs.meta
new file mode 100644
index 00000000..cb3ff581
--- /dev/null
+++ b/Assets/External/websocket-sharp/Server/WebSocketSessionManager.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 333da605558912f45a17f746792fd2dc
\ No newline at end of file
diff --git a/Assets/External/websocket-sharp/WebSocket.cs b/Assets/External/websocket-sharp/WebSocket.cs
new file mode 100644
index 00000000..002eb736
--- /dev/null
+++ b/Assets/External/websocket-sharp/WebSocket.cs
@@ -0,0 +1,4320 @@
+#region License
+/*
+ * WebSocket.cs
+ *
+ * This code is derived from WebSocket.java
+ * (http://github.com/adamac/Java-WebSocket-client).
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2009 Adam MacBeth
+ * Copyright (c) 2010-2025 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
+
+#region Contributors
+/*
+ * Contributors:
+ * - Frank Razenberg
+ * - David Wood
+ * - Liryna
+ */
+#endregion
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Diagnostics;
+using System.IO;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading;
+using WebSocketSharp.Net;
+using WebSocketSharp.Net.WebSockets;
+
+namespace WebSocketSharp
+{
+ ///
+ /// Implements the WebSocket interface.
+ ///
+ ///
+ ///
+ /// This class provides a set of methods and properties for two-way
+ /// communication using the WebSocket protocol.
+ ///
+ ///
+ /// The WebSocket protocol is defined in
+ /// RFC 6455.
+ ///
+ ///
+ public class WebSocket : IDisposable
+ {
+ #region Private Fields
+
+ private AuthenticationChallenge _authChallenge;
+ private string _base64Key;
+ private Action _closeContext;
+ private CompressionMethod _compression;
+ private WebSocketContext _context;
+ private CookieCollection _cookies;
+ private NetworkCredential _credentials;
+ private bool _emitOnPing;
+ private static readonly byte[] _emptyBytes;
+ private bool _enableRedirection;
+ private string _extensions;
+ private bool _extensionsRequested;
+ private object _forMessageEventQueue;
+ private object _forPing;
+ private object _forSend;
+ private object _forState;
+ private MemoryStream _fragmentsBuffer;
+ private bool _fragmentsCompressed;
+ private Opcode _fragmentsOpcode;
+ private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ private Func _handshakeRequestChecker;
+ private bool _ignoreExtensions;
+ private bool _inContinuation;
+ private volatile bool _inMessage;
+ private bool _isClient;
+ private bool _isSecure;
+ private volatile Logger _log;
+ private static readonly int _maxRetryCountForConnect;
+ private Action _message;
+ private Queue _messageEventQueue;
+ private bool _noDelay;
+ private uint _nonceCount;
+ private string _origin;
+ private ManualResetEvent _pongReceived;
+ private bool _preAuth;
+ private string _protocol;
+ private string[] _protocols;
+ private bool _protocolsRequested;
+ private NetworkCredential _proxyCredentials;
+ private Uri _proxyUri;
+ private volatile WebSocketState _readyState;
+ private ManualResetEvent _receivingExited;
+ private int _retryCountForConnect;
+ private Socket _socket;
+ private ClientSslConfiguration _sslConfig;
+ private Stream _stream;
+ private TcpClient _tcpClient;
+ private Uri _uri;
+ private const string _version = "13";
+ private TimeSpan _waitTime;
+
+ #endregion
+
+ #region Internal Fields
+
+ ///
+ /// Represents the length used to determine whether the data should
+ /// be fragmented in sending.
+ ///
+ ///
+ ///
+ /// The data will be fragmented if its length is greater than
+ /// the value of this field.
+ ///
+ ///
+ /// If you would like to change the value, you must set it to
+ /// a value between 125 and Int32.MaxValue - 14 inclusive.
+ ///
+ ///
+ internal static readonly int FragmentLength;
+
+ ///
+ /// Represents the random number generator used internally.
+ ///
+ internal static readonly RandomNumberGenerator RandomNumber;
+
+ #endregion
+
+ #region Static Constructor
+
+ static WebSocket ()
+ {
+ _emptyBytes = new byte[0];
+ _maxRetryCountForConnect = 10;
+
+ FragmentLength = 1016;
+ RandomNumber = new RNGCryptoServiceProvider ();
+ }
+
+ #endregion
+
+ #region Internal Constructors
+
+ // As server
+ internal WebSocket (HttpListenerWebSocketContext context, string protocol)
+ {
+ _context = context;
+ _protocol = protocol;
+
+ _closeContext = context.Close;
+ _isSecure = context.IsSecureConnection;
+ _log = context.Log;
+ _message = messages;
+ _socket = context.Socket;
+ _stream = context.Stream;
+ _waitTime = TimeSpan.FromSeconds (1);
+
+ init ();
+ }
+
+ // As server
+ internal WebSocket (TcpListenerWebSocketContext context, string protocol)
+ {
+ _context = context;
+ _protocol = protocol;
+
+ _closeContext = context.Close;
+ _isSecure = context.IsSecureConnection;
+ _log = context.Log;
+ _message = messages;
+ _socket = context.Socket;
+ _stream = context.Stream;
+ _waitTime = TimeSpan.FromSeconds (1);
+
+ init ();
+ }
+
+ #endregion
+
+ #region Public Constructors
+
+ ///
+ /// Initializes a new instance of the class with
+ /// the specified URL and optionally subprotocols.
+ ///
+ ///
+ ///
+ /// A that specifies the URL to which to connect.
+ ///
+ ///
+ /// The scheme of the URL must be ws or wss.
+ ///
+ ///
+ /// The new instance uses a secure connection if the scheme is wss.
+ ///
+ ///
+ ///
+ ///
+ /// An array of that specifies the names of
+ /// the subprotocols if necessary.
+ ///
+ ///
+ /// Each value of the array must be a token defined in
+ ///
+ /// RFC 2616.
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is an invalid WebSocket URL string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains a value that is not a token.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains a value twice.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ public WebSocket (string url, params string[] protocols)
+ {
+ if (url == null)
+ throw new ArgumentNullException ("url");
+
+ if (url.Length == 0)
+ throw new ArgumentException ("An empty string.", "url");
+
+ string msg;
+
+ if (!url.TryCreateWebSocketUri (out _uri, out msg))
+ throw new ArgumentException (msg, "url");
+
+ if (protocols != null && protocols.Length > 0) {
+ if (!checkProtocols (protocols, out msg))
+ throw new ArgumentException (msg, "protocols");
+
+ _protocols = protocols;
+ }
+
+ _base64Key = CreateBase64Key ();
+ _isClient = true;
+ _isSecure = _uri.Scheme == "wss";
+ _log = new Logger ();
+ _message = messagec;
+ _retryCountForConnect = -1;
+ _waitTime = TimeSpan.FromSeconds (5);
+
+ init ();
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal CookieCollection CookieCollection {
+ get {
+ return _cookies;
+ }
+ }
+
+ // As server
+ internal Func CustomHandshakeRequestChecker {
+ get {
+ return _handshakeRequestChecker;
+ }
+
+ set {
+ _handshakeRequestChecker = value;
+ }
+ }
+
+ // As server
+ internal bool IgnoreExtensions {
+ get {
+ return _ignoreExtensions;
+ }
+
+ set {
+ _ignoreExtensions = value;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets or sets the compression method used to compress a message.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It indicates the compression method used to compress a message.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ ///
+ ///
+ /// The set operation is not available if the interface is not for
+ /// the client.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The set operation is not available when the current state of
+ /// the interface is neither New nor Closed.
+ ///
+ ///
+ public CompressionMethod Compression {
+ get {
+ return _compression;
+ }
+
+ set {
+ if (!_isClient) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ lock (_forState) {
+ if (!canSet ()) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _compression = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets the HTTP cookies included in the handshake request/response.
+ ///
+ ///
+ ///
+ /// An
+ /// instance.
+ ///
+ ///
+ /// It provides an enumerator which supports the iteration over
+ /// the collection of the cookies.
+ ///
+ ///
+ public IEnumerable Cookies {
+ get {
+ lock (_cookies.SyncRoot) {
+ foreach (var cookie in _cookies)
+ yield return cookie;
+ }
+ }
+ }
+
+ ///
+ /// Gets the credentials for the HTTP authentication (Basic/Digest).
+ ///
+ ///
+ ///
+ /// A that represents the credentials
+ /// used to authenticate the client.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ public NetworkCredential Credentials {
+ get {
+ return _credentials;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the interface emits
+ /// the message event when it receives a ping.
+ ///
+ ///
+ ///
+ /// true if the interface emits the message event when
+ /// it receives a ping; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ ///
+ /// The set operation is not available when the current state of
+ /// the interface is neither New nor Closed.
+ ///
+ public bool EmitOnPing {
+ get {
+ return _emitOnPing;
+ }
+
+ set {
+ lock (_forState) {
+ if (!canSet ()) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _emitOnPing = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the URL redirection for
+ /// the handshake request is allowed.
+ ///
+ ///
+ ///
+ /// true if the interface allows the URL redirection for
+ /// the handshake request; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ ///
+ ///
+ /// The set operation is not available if the interface is not for
+ /// the client.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The set operation is not available when the current state of
+ /// the interface is neither New nor Closed.
+ ///
+ ///
+ public bool EnableRedirection {
+ get {
+ return _enableRedirection;
+ }
+
+ set {
+ if (!_isClient) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ lock (_forState) {
+ if (!canSet ()) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _enableRedirection = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets the extensions selected by the server.
+ ///
+ ///
+ ///
+ /// A that represents a list of the extensions
+ /// negotiated between the client and server.
+ ///
+ ///
+ /// An empty string if not specified or selected.
+ ///
+ ///
+ public string Extensions {
+ get {
+ return _extensions ?? String.Empty;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the communication is possible.
+ ///
+ ///
+ /// true if the communication is possible; otherwise, false.
+ ///
+ public bool IsAlive {
+ get {
+ return ping (_emptyBytes);
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the connection is secure.
+ ///
+ ///
+ /// true if the connection is secure; otherwise, false.
+ ///
+ public bool IsSecure {
+ get {
+ return _isSecure;
+ }
+ }
+
+ ///
+ /// Gets the logging function.
+ ///
+ ///
+ /// The default logging level is .
+ ///
+ ///
+ /// A that provides the logging function.
+ ///
+ ///
+ /// The get operation is not available if the interface is not for
+ /// the client.
+ ///
+ public Logger Log {
+ get {
+ if (!_isClient) {
+ var msg = "The get operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _log;
+ }
+
+ internal set {
+ _log = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the underlying TCP socket
+ /// disables a delay when send or receive buffer is not full.
+ ///
+ ///
+ ///
+ /// true if the delay is disabled; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ ///
+ ///
+ /// The set operation is not available when the current state of
+ /// the interface is neither New nor Closed.
+ ///
+ public bool NoDelay {
+ get {
+ return _noDelay;
+ }
+
+ set {
+ lock (_forState) {
+ if (!canSet ()) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _noDelay = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the value of the HTTP Origin header to send with
+ /// the handshake request.
+ ///
+ ///
+ ///
+ /// The HTTP Origin header is defined in
+ ///
+ /// Section 7 of RFC 6454.
+ ///
+ ///
+ /// The interface sends the Origin header if this property has any.
+ ///
+ ///
+ ///
+ ///
+ /// A that represents the value of the Origin
+ /// header to send.
+ ///
+ ///
+ /// The syntax is <scheme>://<host>[:<port>].
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ ///
+ ///
+ /// The value specified for a set operation is not an absolute URI string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The value specified for a set operation includes the path segments.
+ ///
+ ///
+ ///
+ ///
+ /// The set operation is not available if the interface is not for
+ /// the client.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The set operation is not available when the current state of
+ /// the interface is neither New nor Closed.
+ ///
+ ///
+ public string Origin {
+ get {
+ return _origin;
+ }
+
+ set {
+ if (!_isClient) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (!value.IsNullOrEmpty ()) {
+ Uri uri;
+
+ if (!Uri.TryCreate (value, UriKind.Absolute, out uri)) {
+ var msg = "Not an absolute URI string.";
+
+ throw new ArgumentException (msg, "value");
+ }
+
+ if (uri.Segments.Length > 1) {
+ var msg = "It includes the path segments.";
+
+ throw new ArgumentException (msg, "value");
+ }
+ }
+
+ lock (_forState) {
+ if (!canSet ()) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _origin = !value.IsNullOrEmpty () ? value.TrimEnd ('/') : value;
+ }
+ }
+ }
+
+ ///
+ /// Gets the name of subprotocol selected by the server.
+ ///
+ ///
+ ///
+ /// A that will be one of the names of
+ /// subprotocols specified by client.
+ ///
+ ///
+ /// An empty string if not specified or selected.
+ ///
+ ///
+ public string Protocol {
+ get {
+ return _protocol ?? String.Empty;
+ }
+
+ internal set {
+ _protocol = value;
+ }
+ }
+
+ ///
+ /// Gets the current state of the interface.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It indicates the current state of the interface.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ public WebSocketState ReadyState {
+ get {
+ return _readyState;
+ }
+ }
+
+ ///
+ /// Gets the configuration for secure connection.
+ ///
+ ///
+ /// The configuration is used when the interface attempts to connect,
+ /// so it must be configured before any connect method is called.
+ ///
+ ///
+ /// A that represents the
+ /// configuration used to establish a secure connection.
+ ///
+ ///
+ ///
+ /// The get operation is not available if the interface is not for
+ /// the client.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The get operation is not available if the interface does not use
+ /// a secure connection.
+ ///
+ ///
+ public ClientSslConfiguration SslConfiguration {
+ get {
+ if (!_isClient) {
+ var msg = "The get operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (!_isSecure) {
+ var msg = "The get operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return getSslConfiguration ();
+ }
+ }
+
+ ///
+ /// Gets the URL to which to connect.
+ ///
+ ///
+ ///
+ /// A that represents the URL to which to connect.
+ ///
+ ///
+ /// Also it represents the URL requested by the client if the interface
+ /// is for the server.
+ ///
+ ///
+ public Uri Url {
+ get {
+ return _isClient ? _uri : _context.RequestUri;
+ }
+ }
+
+ ///
+ /// Gets or sets the time to wait for the response to the ping or close.
+ ///
+ ///
+ ///
+ /// A that represents the time to wait for
+ /// the response.
+ ///
+ ///
+ /// The default value is the same as 5 seconds if the interface is
+ /// for the client.
+ ///
+ ///
+ ///
+ /// The value specified for a set operation is zero or less.
+ ///
+ ///
+ /// The set operation is not available when the current state of
+ /// the interface is neither New nor Closed.
+ ///
+ public TimeSpan WaitTime {
+ get {
+ return _waitTime;
+ }
+
+ set {
+ if (value <= TimeSpan.Zero) {
+ var msg = "Zero or less.";
+
+ throw new ArgumentOutOfRangeException ("value", msg);
+ }
+
+ lock (_forState) {
+ if (!canSet ()) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _waitTime = value;
+ }
+ }
+ }
+
+ #endregion
+
+ #region Public Events
+
+ ///
+ /// Occurs when the connection has been closed.
+ ///
+ public event EventHandler OnClose;
+
+ ///
+ /// Occurs when the interface gets an error.
+ ///
+ public event EventHandler OnError;
+
+ ///
+ /// Occurs when the interface receives a message.
+ ///
+ public event EventHandler OnMessage;
+
+ ///
+ /// Occurs when the connection has been established.
+ ///
+ public event EventHandler OnOpen;
+
+ #endregion
+
+ #region Private Methods
+
+ private void abort (string reason, Exception exception)
+ {
+ var code = exception is WebSocketException
+ ? ((WebSocketException) exception).Code
+ : (ushort) 1006;
+
+ abort (code, reason);
+ }
+
+ private void abort (ushort code, string reason)
+ {
+ var data = new PayloadData (code, reason);
+
+ close (data, false, false);
+ }
+
+ // As server
+ private bool accept ()
+ {
+ lock (_forState) {
+ if (_readyState == WebSocketState.Open) {
+ _log.Trace ("The connection has already been established.");
+
+ return false;
+ }
+
+ if (_readyState == WebSocketState.Closing) {
+ _log.Error ("The close process is in progress.");
+
+ error ("An error has occurred before accepting.", null);
+
+ return false;
+ }
+
+ if (_readyState == WebSocketState.Closed) {
+ _log.Error ("The connection has been closed.");
+
+ error ("An error has occurred before accepting.", null);
+
+ return false;
+ }
+
+ _readyState = WebSocketState.Connecting;
+
+ var accepted = false;
+
+ try {
+ accepted = acceptHandshake ();
+ }
+ catch (Exception ex) {
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ abort (1011, "An exception has occurred while accepting.");
+ }
+
+ if (!accepted)
+ return false;
+
+ _readyState = WebSocketState.Open;
+
+ return true;
+ }
+ }
+
+ // As server
+ private bool acceptHandshake ()
+ {
+ string msg;
+
+ if (!checkHandshakeRequest (_context, out msg)) {
+ _log.Error (msg);
+ _log.Debug (_context.ToString ());
+
+ refuseHandshake (1002, "A handshake error has occurred.");
+
+ return false;
+ }
+
+ if (!customCheckHandshakeRequest (_context, out msg)) {
+ _log.Error (msg);
+ _log.Debug (_context.ToString ());
+
+ refuseHandshake (1002, "A handshake error has occurred.");
+
+ return false;
+ }
+
+ _base64Key = _context.Headers["Sec-WebSocket-Key"];
+
+ if (_protocol != null) {
+ var matched = _context
+ .SecWebSocketProtocols
+ .Contains (p => p == _protocol);
+
+ if (!matched)
+ _protocol = null;
+ }
+
+ if (!_ignoreExtensions) {
+ var val = _context.Headers["Sec-WebSocket-Extensions"];
+
+ processSecWebSocketExtensionsClientHeader (val);
+ }
+
+ if (_noDelay)
+ _socket.NoDelay = true;
+
+ createHandshakeResponse ().WriteTo (_stream);
+
+ return true;
+ }
+
+ private bool canSet ()
+ {
+ return _readyState == WebSocketState.New
+ || _readyState == WebSocketState.Closed;
+ }
+
+ // As server
+ private bool checkHandshakeRequest (
+ WebSocketContext context,
+ out string message
+ )
+ {
+ message = null;
+
+ if (!context.IsWebSocketRequest) {
+ message = "Not a WebSocket handshake request.";
+
+ return false;
+ }
+
+ var headers = context.Headers;
+
+ var key = headers["Sec-WebSocket-Key"];
+
+ if (key == null) {
+ message = "The Sec-WebSocket-Key header is non-existent.";
+
+ return false;
+ }
+
+ if (key.Length == 0) {
+ message = "The Sec-WebSocket-Key header is invalid.";
+
+ return false;
+ }
+
+ var ver = headers["Sec-WebSocket-Version"];
+
+ if (ver == null) {
+ message = "The Sec-WebSocket-Version header is non-existent.";
+
+ return false;
+ }
+
+ if (ver != _version) {
+ message = "The Sec-WebSocket-Version header is invalid.";
+
+ return false;
+ }
+
+ var subps = headers["Sec-WebSocket-Protocol"];
+
+ if (subps != null) {
+ if (subps.Length == 0) {
+ message = "The Sec-WebSocket-Protocol header is invalid.";
+
+ return false;
+ }
+ }
+
+ if (!_ignoreExtensions) {
+ var exts = headers["Sec-WebSocket-Extensions"];
+
+ if (exts != null) {
+ if (exts.Length == 0) {
+ message = "The Sec-WebSocket-Extensions header is invalid.";
+
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ // As client
+ private bool checkHandshakeResponse (
+ HttpResponse response,
+ out string message
+ )
+ {
+ message = null;
+
+ if (response.IsRedirect) {
+ message = "The redirection is indicated.";
+
+ return false;
+ }
+
+ if (response.IsUnauthorized) {
+ message = "The authentication is required.";
+
+ return false;
+ }
+
+ if (!response.IsWebSocketResponse) {
+ message = "Not a WebSocket handshake response.";
+
+ return false;
+ }
+
+ var headers = response.Headers;
+
+ var key = headers["Sec-WebSocket-Accept"];
+
+ if (key == null) {
+ message = "The Sec-WebSocket-Accept header is non-existent.";
+
+ return false;
+ }
+
+ if (key != CreateResponseKey (_base64Key)) {
+ message = "The Sec-WebSocket-Accept header is invalid.";
+
+ return false;
+ }
+
+ var ver = headers["Sec-WebSocket-Version"];
+
+ if (ver != null) {
+ if (ver != _version) {
+ message = "The Sec-WebSocket-Version header is invalid.";
+
+ return false;
+ }
+ }
+
+ var subp = headers["Sec-WebSocket-Protocol"];
+
+ if (subp == null) {
+ if (_protocolsRequested) {
+ message = "The Sec-WebSocket-Protocol header is non-existent.";
+
+ return false;
+ }
+ }
+ else {
+ var isValid = _protocolsRequested
+ && subp.Length > 0
+ && _protocols.Contains (p => p == subp);
+
+ if (!isValid) {
+ message = "The Sec-WebSocket-Protocol header is invalid.";
+
+ return false;
+ }
+ }
+
+ var exts = headers["Sec-WebSocket-Extensions"];
+
+ if (exts != null) {
+ if (!validateSecWebSocketExtensionsServerHeader (exts)) {
+ message = "The Sec-WebSocket-Extensions header is invalid.";
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static bool checkProtocols (string[] protocols, out string message)
+ {
+ message = null;
+
+ Func cond = p => p.IsNullOrEmpty () || !p.IsToken ();
+
+ if (protocols.Contains (cond)) {
+ message = "It contains a value that is not a token.";
+
+ return false;
+ }
+
+ if (protocols.ContainsTwice ()) {
+ message = "It contains a value twice.";
+
+ return false;
+ }
+
+ return true;
+ }
+
+ // As client
+ private bool checkProxyConnectResponse (
+ HttpResponse response,
+ out string message
+ )
+ {
+ message = null;
+
+ if (response.IsProxyAuthenticationRequired) {
+ message = "The proxy authentication is required.";
+
+ return false;
+ }
+
+ if (!response.IsSuccess) {
+ message = "The proxy has failed a connection to the requested URL.";
+
+ return false;
+ }
+
+ return true;
+ }
+
+ private bool checkReceivedFrame (WebSocketFrame frame, out string message)
+ {
+ message = null;
+
+ if (frame.IsMasked) {
+ if (_isClient) {
+ message = "A frame from the server is masked.";
+
+ return false;
+ }
+ }
+ else {
+ if (!_isClient) {
+ message = "A frame from a client is not masked.";
+
+ return false;
+ }
+ }
+
+ if (frame.IsCompressed) {
+ if (_compression == CompressionMethod.None) {
+ message = "A frame is compressed without any agreement for it.";
+
+ return false;
+ }
+
+ if (!frame.IsData) {
+ message = "A non data frame is compressed.";
+
+ return false;
+ }
+ }
+
+ if (frame.IsData) {
+ if (_inContinuation) {
+ message = "A data frame was received while receiving continuation frames.";
+
+ return false;
+ }
+ }
+
+ if (frame.IsControl) {
+ if (frame.Fin == Fin.More) {
+ message = "A control frame is fragmented.";
+
+ return false;
+ }
+
+ if (frame.PayloadLength > 125) {
+ message = "The payload length of a control frame is greater than 125.";
+
+ return false;
+ }
+ }
+
+ if (frame.Rsv2 == Rsv.On) {
+ message = "The RSV2 of a frame is non-zero without any negotiation for it.";
+
+ return false;
+ }
+
+ if (frame.Rsv3 == Rsv.On) {
+ message = "The RSV3 of a frame is non-zero without any negotiation for it.";
+
+ return false;
+ }
+
+ return true;
+ }
+
+ private void close (ushort code, string reason)
+ {
+ if (_readyState == WebSocketState.Closing) {
+ _log.Trace ("The close process is already in progress.");
+
+ return;
+ }
+
+ if (_readyState == WebSocketState.Closed) {
+ _log.Trace ("The connection has already been closed.");
+
+ return;
+ }
+
+ if (code == 1005) {
+ close (PayloadData.Empty, true, false);
+
+ return;
+ }
+
+ var data = new PayloadData (code, reason);
+ var send = !code.IsReservedStatusCode ();
+
+ close (data, send, false);
+ }
+
+ private void close (PayloadData payloadData, bool send, bool received)
+ {
+ lock (_forState) {
+ if (_readyState == WebSocketState.Closing) {
+ _log.Trace ("The close process is already in progress.");
+
+ return;
+ }
+
+ if (_readyState == WebSocketState.Closed) {
+ _log.Trace ("The connection has already been closed.");
+
+ return;
+ }
+
+ send = send && _readyState == WebSocketState.Open;
+
+ _readyState = WebSocketState.Closing;
+ }
+
+ _log.Trace ("Begin closing the connection.");
+
+ var res = closeHandshake (payloadData, send, received);
+
+ releaseResources ();
+
+ _log.Trace ("End closing the connection.");
+
+ _readyState = WebSocketState.Closed;
+
+ var e = new CloseEventArgs (payloadData, res);
+
+ try {
+ OnClose.Emit (this, e);
+ }
+ catch (Exception ex) {
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
+ }
+ }
+
+ private void closeAsync (ushort code, string reason)
+ {
+ if (_readyState == WebSocketState.Closing) {
+ _log.Trace ("The close process is already in progress.");
+
+ return;
+ }
+
+ if (_readyState == WebSocketState.Closed) {
+ _log.Trace ("The connection has already been closed.");
+
+ return;
+ }
+
+ if (code == 1005) {
+ closeAsync (PayloadData.Empty, true, false);
+
+ return;
+ }
+
+ var data = new PayloadData (code, reason);
+ var send = !code.IsReservedStatusCode ();
+
+ closeAsync (data, send, false);
+ }
+
+ private void closeAsync (PayloadData payloadData, bool send, bool received)
+ {
+ Action closer = close;
+
+ closer.BeginInvoke (
+ payloadData,
+ send,
+ received,
+ ar => closer.EndInvoke (ar),
+ null
+ );
+ }
+
+ private bool closeHandshake (
+ PayloadData payloadData,
+ bool send,
+ bool received
+ )
+ {
+ var sent = false;
+
+ if (send) {
+ var frame = WebSocketFrame.CreateCloseFrame (payloadData, _isClient);
+ var bytes = frame.ToArray ();
+
+ sent = sendBytes (bytes);
+
+ if (_isClient)
+ frame.Unmask ();
+ }
+
+ var wait = !received && sent && _receivingExited != null;
+
+ if (wait)
+ received = _receivingExited.WaitOne (_waitTime);
+
+ var ret = sent && received;
+
+ var msg = String.Format (
+ "The closing was clean? {0} (sent: {1} received: {2})",
+ ret,
+ sent,
+ received
+ );
+
+ _log.Debug (msg);
+
+ return ret;
+ }
+
+ // As client
+ private bool connect ()
+ {
+ if (_readyState == WebSocketState.Connecting) {
+ _log.Trace ("The connect process is in progress.");
+
+ return false;
+ }
+
+ lock (_forState) {
+ if (_readyState == WebSocketState.Open) {
+ _log.Trace ("The connection has already been established.");
+
+ return false;
+ }
+
+ if (_readyState == WebSocketState.Closing) {
+ _log.Error ("The close process is in progress.");
+
+ error ("An error has occurred before connecting.", null);
+
+ return false;
+ }
+
+ if (_retryCountForConnect >= _maxRetryCountForConnect) {
+ _log.Error ("An opportunity for reconnecting has been lost.");
+
+ error ("An error has occurred before connecting.", null);
+
+ return false;
+ }
+
+ _retryCountForConnect++;
+
+ _readyState = WebSocketState.Connecting;
+
+ var done = false;
+
+ try {
+ done = doHandshake ();
+ }
+ catch (Exception ex) {
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ abort ("An exception has occurred while connecting.", ex);
+ }
+
+ if (!done)
+ return false;
+
+ _retryCountForConnect = -1;
+
+ _readyState = WebSocketState.Open;
+
+ return true;
+ }
+ }
+
+ // As client
+ private AuthenticationResponse createAuthenticationResponse ()
+ {
+ if (_credentials == null)
+ return null;
+
+ if (_authChallenge == null)
+ return _preAuth ? new AuthenticationResponse (_credentials) : null;
+
+ var ret = new AuthenticationResponse (
+ _authChallenge,
+ _credentials,
+ _nonceCount
+ );
+
+ _nonceCount = ret.NonceCount;
+
+ return ret;
+ }
+
+ // As client
+ private string createExtensions ()
+ {
+ var buff = new StringBuilder (80);
+
+ if (_compression != CompressionMethod.None) {
+ var str = _compression.ToExtensionString (
+ "server_no_context_takeover",
+ "client_no_context_takeover"
+ );
+
+ buff.AppendFormat ("{0}, ", str);
+ }
+
+ var len = buff.Length;
+
+ if (len <= 2)
+ return null;
+
+ buff.Length = len - 2;
+
+ return buff.ToString ();
+ }
+
+ // As server
+ private HttpResponse createHandshakeFailureResponse ()
+ {
+ var ret = HttpResponse.CreateCloseResponse (HttpStatusCode.BadRequest);
+
+ ret.Headers["Sec-WebSocket-Version"] = _version;
+
+ return ret;
+ }
+
+ // As client
+ private HttpRequest createHandshakeRequest ()
+ {
+ var ret = HttpRequest.CreateWebSocketHandshakeRequest (_uri);
+
+ var headers = ret.Headers;
+
+ headers["Sec-WebSocket-Key"] = _base64Key;
+ headers["Sec-WebSocket-Version"] = _version;
+
+ if (!_origin.IsNullOrEmpty ())
+ headers["Origin"] = _origin;
+
+ if (_protocols != null) {
+ headers["Sec-WebSocket-Protocol"] = _protocols.ToString (", ");
+
+ _protocolsRequested = true;
+ }
+
+ var exts = createExtensions ();
+
+ if (exts != null) {
+ headers["Sec-WebSocket-Extensions"] = exts;
+
+ _extensionsRequested = true;
+ }
+
+ var ares = createAuthenticationResponse ();
+
+ if (ares != null)
+ headers["Authorization"] = ares.ToString ();
+
+ if (_cookies.Count > 0)
+ ret.SetCookies (_cookies);
+
+ return ret;
+ }
+
+ // As server
+ private HttpResponse createHandshakeResponse ()
+ {
+ var ret = HttpResponse.CreateWebSocketHandshakeResponse ();
+
+ var headers = ret.Headers;
+
+ headers["Sec-WebSocket-Accept"] = CreateResponseKey (_base64Key);
+
+ if (_protocol != null)
+ headers["Sec-WebSocket-Protocol"] = _protocol;
+
+ if (_extensions != null)
+ headers["Sec-WebSocket-Extensions"] = _extensions;
+
+ if (_cookies.Count > 0)
+ ret.SetCookies (_cookies);
+
+ return ret;
+ }
+
+ // As client
+ private TcpClient createTcpClient (string hostname, int port)
+ {
+ var ret = new TcpClient (hostname, port);
+
+ if (_noDelay)
+ ret.NoDelay = true;
+
+ return ret;
+ }
+
+ // As server
+ private bool customCheckHandshakeRequest (
+ WebSocketContext context,
+ out string message
+ )
+ {
+ message = null;
+
+ if (_handshakeRequestChecker == null)
+ return true;
+
+ message = _handshakeRequestChecker (context);
+
+ return message == null;
+ }
+
+ private MessageEventArgs dequeueFromMessageEventQueue ()
+ {
+ lock (_forMessageEventQueue) {
+ return _messageEventQueue.Count > 0
+ ? _messageEventQueue.Dequeue ()
+ : null;
+ }
+ }
+
+ // As client
+ private bool doHandshake ()
+ {
+ setClientStream ();
+
+ var res = sendHandshakeRequest ();
+
+ string msg;
+
+ if (!checkHandshakeResponse (res, out msg)) {
+ _log.Error (msg);
+ _log.Debug (res.ToString ());
+
+ abort (1002, "A handshake error has occurred.");
+
+ return false;
+ }
+
+ if (_protocolsRequested)
+ _protocol = res.Headers["Sec-WebSocket-Protocol"];
+
+ if (_extensionsRequested) {
+ var exts = res.Headers["Sec-WebSocket-Extensions"];
+
+ if (exts == null)
+ _compression = CompressionMethod.None;
+ else
+ _extensions = exts;
+ }
+
+ var cookies = res.Cookies;
+
+ if (cookies.Count > 0)
+ _cookies.SetOrRemove (cookies);
+
+ return true;
+ }
+
+ private void enqueueToMessageEventQueue (MessageEventArgs e)
+ {
+ lock (_forMessageEventQueue)
+ _messageEventQueue.Enqueue (e);
+ }
+
+ private void error (string message, Exception exception)
+ {
+ var e = new ErrorEventArgs (message, exception);
+
+ try {
+ OnError.Emit (this, e);
+ }
+ catch (Exception ex) {
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
+ }
+ }
+
+ private ClientSslConfiguration getSslConfiguration ()
+ {
+ if (_sslConfig == null)
+ _sslConfig = new ClientSslConfiguration (_uri.DnsSafeHost);
+
+ return _sslConfig;
+ }
+
+ private void init ()
+ {
+ _compression = CompressionMethod.None;
+ _cookies = new CookieCollection ();
+ _forPing = new object ();
+ _forSend = new object ();
+ _forState = new object ();
+ _messageEventQueue = new Queue ();
+ _forMessageEventQueue = ((ICollection) _messageEventQueue).SyncRoot;
+ _readyState = WebSocketState.New;
+ }
+
+ private void message ()
+ {
+ MessageEventArgs e = null;
+
+ lock (_forMessageEventQueue) {
+ if (_inMessage)
+ return;
+
+ if (_messageEventQueue.Count == 0)
+ return;
+
+ if (_readyState != WebSocketState.Open)
+ return;
+
+ e = _messageEventQueue.Dequeue ();
+
+ _inMessage = true;
+ }
+
+ _message (e);
+ }
+
+ private void messagec (MessageEventArgs e)
+ {
+ do {
+ try {
+ OnMessage.Emit (this, e);
+ }
+ catch (Exception ex) {
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ error ("An exception has occurred during an OnMessage event.", ex);
+ }
+
+ lock (_forMessageEventQueue) {
+ if (_messageEventQueue.Count == 0) {
+ _inMessage = false;
+
+ break;
+ }
+
+ if (_readyState != WebSocketState.Open) {
+ _inMessage = false;
+
+ break;
+ }
+
+ e = _messageEventQueue.Dequeue ();
+ }
+ }
+ while (true);
+ }
+
+ private void messages (MessageEventArgs e)
+ {
+ try {
+ OnMessage.Emit (this, e);
+ }
+ catch (Exception ex) {
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ error ("An exception has occurred during an OnMessage event.", ex);
+ }
+
+ lock (_forMessageEventQueue) {
+ if (_messageEventQueue.Count == 0) {
+ _inMessage = false;
+
+ return;
+ }
+
+ if (_readyState != WebSocketState.Open) {
+ _inMessage = false;
+
+ return;
+ }
+
+ e = _messageEventQueue.Dequeue ();
+ }
+
+ ThreadPool.QueueUserWorkItem (state => messages (e));
+ }
+
+ private void open ()
+ {
+ _inMessage = true;
+
+ startReceiving ();
+
+ try {
+ OnOpen.Emit (this, EventArgs.Empty);
+ }
+ catch (Exception ex) {
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ error ("An exception has occurred during the OnOpen event.", ex);
+ }
+
+ MessageEventArgs e = null;
+
+ lock (_forMessageEventQueue) {
+ if (_messageEventQueue.Count == 0) {
+ _inMessage = false;
+
+ return;
+ }
+
+ if (_readyState != WebSocketState.Open) {
+ _inMessage = false;
+
+ return;
+ }
+
+ e = _messageEventQueue.Dequeue ();
+ }
+
+ _message.BeginInvoke (e, ar => _message.EndInvoke (ar), null);
+ }
+
+ private bool ping (byte[] data)
+ {
+ if (_readyState != WebSocketState.Open)
+ return false;
+
+ var received = _pongReceived;
+
+ if (received == null)
+ return false;
+
+ lock (_forPing) {
+ try {
+ received.Reset ();
+
+ var sent = send (Fin.Final, Opcode.Ping, data, false);
+
+ if (!sent)
+ return false;
+
+ return received.WaitOne (_waitTime);
+ }
+ catch (ObjectDisposedException) {
+ return false;
+ }
+ }
+ }
+
+ private bool processCloseFrame (WebSocketFrame frame)
+ {
+ var data = frame.PayloadData;
+ var send = !data.HasReservedCode;
+
+ close (data, send, true);
+
+ return false;
+ }
+
+ private bool processDataFrame (WebSocketFrame frame)
+ {
+ var e = frame.IsCompressed
+ ? new MessageEventArgs (
+ frame.Opcode,
+ frame.PayloadData.ApplicationData.Decompress (_compression)
+ )
+ : new MessageEventArgs (frame);
+
+ enqueueToMessageEventQueue (e);
+
+ return true;
+ }
+
+ private bool processFragmentFrame (WebSocketFrame frame)
+ {
+ if (!_inContinuation) {
+ if (frame.IsContinuation)
+ return true;
+
+ _fragmentsOpcode = frame.Opcode;
+ _fragmentsCompressed = frame.IsCompressed;
+ _fragmentsBuffer = new MemoryStream ();
+ _inContinuation = true;
+ }
+
+ _fragmentsBuffer.WriteBytes (frame.PayloadData.ApplicationData, 1024);
+
+ if (frame.IsFinal) {
+ using (_fragmentsBuffer) {
+ var data = _fragmentsCompressed
+ ? _fragmentsBuffer.DecompressToArray (_compression)
+ : _fragmentsBuffer.ToArray ();
+
+ var e = new MessageEventArgs (_fragmentsOpcode, data);
+
+ enqueueToMessageEventQueue (e);
+ }
+
+ _fragmentsBuffer = null;
+ _inContinuation = false;
+ }
+
+ return true;
+ }
+
+ private bool processPingFrame (WebSocketFrame frame)
+ {
+ _log.Trace ("A ping was received.");
+
+ var pong = WebSocketFrame.CreatePongFrame (frame.PayloadData, _isClient);
+
+ lock (_forState) {
+ if (_readyState != WebSocketState.Open) {
+ _log.Trace ("A pong to this ping cannot be sent.");
+
+ return true;
+ }
+
+ var bytes = pong.ToArray ();
+ var sent = sendBytes (bytes);
+
+ if (!sent)
+ return false;
+ }
+
+ _log.Trace ("A pong to this ping has been sent.");
+
+ if (_emitOnPing) {
+ if (_isClient)
+ pong.Unmask ();
+
+ var e = new MessageEventArgs (frame);
+
+ enqueueToMessageEventQueue (e);
+ }
+
+ return true;
+ }
+
+ private bool processPongFrame (WebSocketFrame frame)
+ {
+ _log.Trace ("A pong was received.");
+
+ try {
+ _pongReceived.Set ();
+ }
+ catch (NullReferenceException) {
+ return false;
+ }
+ catch (ObjectDisposedException) {
+ return false;
+ }
+
+ _log.Trace ("It has been signaled.");
+
+ return true;
+ }
+
+ private bool processReceivedFrame (WebSocketFrame frame)
+ {
+ string msg;
+
+ if (!checkReceivedFrame (frame, out msg)) {
+ _log.Error (msg);
+ _log.Debug (frame.ToString (false));
+
+ abort (1002, "An error has occurred while receiving.");
+
+ return false;
+ }
+
+ frame.Unmask ();
+
+ return frame.IsFragment
+ ? processFragmentFrame (frame)
+ : frame.IsData
+ ? processDataFrame (frame)
+ : frame.IsPing
+ ? processPingFrame (frame)
+ : frame.IsPong
+ ? processPongFrame (frame)
+ : frame.IsClose
+ ? processCloseFrame (frame)
+ : processUnsupportedFrame (frame);
+ }
+
+ // As server
+ private void processSecWebSocketExtensionsClientHeader (string value)
+ {
+ if (value == null)
+ return;
+
+ var buff = new StringBuilder (80);
+
+ var compRequested = false;
+
+ foreach (var elm in value.SplitHeaderValue (',')) {
+ var ext = elm.Trim ();
+
+ if (ext.Length == 0)
+ continue;
+
+ if (!compRequested) {
+ if (ext.IsCompressionExtension (CompressionMethod.Deflate)) {
+ _compression = CompressionMethod.Deflate;
+
+ var str = _compression.ToExtensionString (
+ "client_no_context_takeover",
+ "server_no_context_takeover"
+ );
+
+ buff.AppendFormat ("{0}, ", str);
+
+ compRequested = true;
+ }
+ }
+ }
+
+ var len = buff.Length;
+
+ if (len <= 2)
+ return;
+
+ buff.Length = len - 2;
+
+ _extensions = buff.ToString ();
+ }
+
+ private bool processUnsupportedFrame (WebSocketFrame frame)
+ {
+ _log.Fatal ("An unsupported frame was received.");
+ _log.Debug (frame.ToString (false));
+
+ abort (1003, "There is no way to handle it.");
+
+ return false;
+ }
+
+ // As server
+ private void refuseHandshake (ushort code, string reason)
+ {
+ createHandshakeFailureResponse ().WriteTo (_stream);
+
+ abort (code, reason);
+ }
+
+ // As client
+ private void releaseClientResources ()
+ {
+ if (_stream != null) {
+ _stream.Dispose ();
+
+ _stream = null;
+ }
+
+ if (_tcpClient != null) {
+ _tcpClient.Close ();
+
+ _tcpClient = null;
+ }
+ }
+
+ private void releaseCommonResources ()
+ {
+ if (_fragmentsBuffer != null) {
+ _fragmentsBuffer.Dispose ();
+
+ _fragmentsBuffer = null;
+ _inContinuation = false;
+ }
+
+ if (_pongReceived != null) {
+ _pongReceived.Close ();
+
+ _pongReceived = null;
+ }
+
+ if (_receivingExited != null) {
+ _receivingExited.Close ();
+
+ _receivingExited = null;
+ }
+ }
+
+ private void releaseResources ()
+ {
+ if (_isClient)
+ releaseClientResources ();
+ else
+ releaseServerResources ();
+
+ releaseCommonResources ();
+ }
+
+ // As server
+ private void releaseServerResources ()
+ {
+ if (_closeContext != null) {
+ _closeContext ();
+
+ _closeContext = null;
+ }
+
+ _stream = null;
+ _context = null;
+ }
+
+ private bool send (byte[] rawFrame)
+ {
+ lock (_forState) {
+ if (_readyState != WebSocketState.Open) {
+ _log.Error ("The current state of the interface is not Open.");
+
+ return false;
+ }
+
+ return sendBytes (rawFrame);
+ }
+ }
+
+ private bool send (Opcode opcode, Stream sourceStream)
+ {
+ lock (_forSend) {
+ var dataStream = sourceStream;
+ var compressed = false;
+ var sent = false;
+
+ try {
+ if (_compression != CompressionMethod.None) {
+ dataStream = sourceStream.Compress (_compression);
+ compressed = true;
+ }
+
+ sent = send (opcode, dataStream, compressed);
+
+ if (!sent)
+ error ("A send has failed.", null);
+ }
+ catch (Exception ex) {
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ error ("An exception has occurred during a send.", ex);
+ }
+ finally {
+ if (compressed)
+ dataStream.Dispose ();
+
+ sourceStream.Dispose ();
+ }
+
+ return sent;
+ }
+ }
+
+ private bool send (Opcode opcode, Stream dataStream, bool compressed)
+ {
+ var len = dataStream.Length;
+
+ if (len == 0)
+ return send (Fin.Final, opcode, _emptyBytes, false);
+
+ var quo = len / FragmentLength;
+ var rem = (int) (len % FragmentLength);
+
+ byte[] buff = null;
+
+ if (quo == 0) {
+ buff = new byte[rem];
+
+ return dataStream.Read (buff, 0, rem) == rem
+ && send (Fin.Final, opcode, buff, compressed);
+ }
+
+ if (quo == 1 && rem == 0) {
+ buff = new byte[FragmentLength];
+
+ return dataStream.Read (buff, 0, FragmentLength) == FragmentLength
+ && send (Fin.Final, opcode, buff, compressed);
+ }
+
+ /* Send fragments */
+
+ // Begin
+
+ buff = new byte[FragmentLength];
+
+ var sent = dataStream.Read (buff, 0, FragmentLength) == FragmentLength
+ && send (Fin.More, opcode, buff, compressed);
+
+ if (!sent)
+ return false;
+
+ // Continue
+
+ var n = rem == 0 ? quo - 2 : quo - 1;
+
+ for (long i = 0; i < n; i++) {
+ sent = dataStream.Read (buff, 0, FragmentLength) == FragmentLength
+ && send (Fin.More, Opcode.Cont, buff, false);
+
+ if (!sent)
+ return false;
+ }
+
+ // End
+
+ if (rem == 0)
+ rem = FragmentLength;
+ else
+ buff = new byte[rem];
+
+ return dataStream.Read (buff, 0, rem) == rem
+ && send (Fin.Final, Opcode.Cont, buff, false);
+ }
+
+ private bool send (Fin fin, Opcode opcode, byte[] data, bool compressed)
+ {
+ var frame = new WebSocketFrame (fin, opcode, data, compressed, _isClient);
+ var rawFrame = frame.ToArray ();
+
+ return send (rawFrame);
+ }
+
+ private void sendAsync (
+ Opcode opcode,
+ Stream sourceStream,
+ Action completed
+ )
+ {
+ Func sender = send;
+
+ sender.BeginInvoke (
+ opcode,
+ sourceStream,
+ ar => {
+ try {
+ var sent = sender.EndInvoke (ar);
+
+ if (completed != null)
+ completed (sent);
+ }
+ catch (Exception ex) {
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ error (
+ "An exception has occurred during the callback for an async send.",
+ ex
+ );
+ }
+ },
+ null
+ );
+ }
+
+ private bool sendBytes (byte[] bytes)
+ {
+ try {
+ _stream.Write (bytes, 0, bytes.Length);
+ }
+ catch (Exception ex) {
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ return false;
+ }
+
+ return true;
+ }
+
+ // As client
+ private HttpResponse sendHandshakeRequest ()
+ {
+ var req = createHandshakeRequest ();
+
+ var timeout = 90000;
+ var res = req.GetResponse (_stream, timeout);
+
+ if (res.IsUnauthorized) {
+ var val = res.Headers["WWW-Authenticate"];
+
+ if (val.IsNullOrEmpty ()) {
+ _log.Debug ("No authentication challenge is specified.");
+
+ return res;
+ }
+
+ var achal = AuthenticationChallenge.Parse (val);
+
+ if (achal == null) {
+ _log.Debug ("An invalid authentication challenge is specified.");
+
+ return res;
+ }
+
+ _authChallenge = achal;
+
+ if (_credentials == null)
+ return res;
+
+ var ares = new AuthenticationResponse (
+ _authChallenge,
+ _credentials,
+ _nonceCount
+ );
+
+ _nonceCount = ares.NonceCount;
+
+ req.Headers["Authorization"] = ares.ToString ();
+
+ if (res.CloseConnection) {
+ releaseClientResources ();
+ setClientStream ();
+ }
+
+ timeout = 15000;
+ res = req.GetResponse (_stream, timeout);
+ }
+
+ if (res.IsRedirect) {
+ if (!_enableRedirection)
+ return res;
+
+ var val = res.Headers["Location"];
+
+ if (val.IsNullOrEmpty ()) {
+ _log.Debug ("No URL to redirect is located.");
+
+ return res;
+ }
+
+ Uri uri;
+ string msg;
+
+ if (!val.TryCreateWebSocketUri (out uri, out msg)) {
+ _log.Debug ("An invalid URL to redirect is located.");
+
+ return res;
+ }
+
+ releaseClientResources ();
+
+ _uri = uri;
+ _isSecure = uri.Scheme == "wss";
+
+ setClientStream ();
+
+ return sendHandshakeRequest ();
+ }
+
+ return res;
+ }
+
+ // As client
+ private HttpResponse sendProxyConnectRequest ()
+ {
+ var req = HttpRequest.CreateConnectRequest (_uri);
+
+ var timeout = 90000;
+ var res = req.GetResponse (_stream, timeout);
+
+ if (res.IsProxyAuthenticationRequired) {
+ if (_proxyCredentials == null)
+ return res;
+
+ var val = res.Headers["Proxy-Authenticate"];
+
+ if (val.IsNullOrEmpty ()) {
+ _log.Debug ("No proxy authentication challenge is specified.");
+
+ return res;
+ }
+
+ var achal = AuthenticationChallenge.Parse (val);
+
+ if (achal == null) {
+ _log.Debug ("An invalid proxy authentication challenge is specified.");
+
+ return res;
+ }
+
+ var ares = new AuthenticationResponse (achal, _proxyCredentials, 0);
+
+ req.Headers["Proxy-Authorization"] = ares.ToString ();
+
+ if (res.CloseConnection) {
+ releaseClientResources ();
+
+ _tcpClient = createTcpClient (_proxyUri.DnsSafeHost, _proxyUri.Port);
+ _stream = _tcpClient.GetStream ();
+ }
+
+ timeout = 15000;
+ res = req.GetResponse (_stream, timeout);
+ }
+
+ return res;
+ }
+
+ // As client
+ private void setClientStream ()
+ {
+ if (_proxyUri != null) {
+ _tcpClient = createTcpClient (_proxyUri.DnsSafeHost, _proxyUri.Port);
+ _stream = _tcpClient.GetStream ();
+
+ var res = sendProxyConnectRequest ();
+
+ string msg;
+
+ if (!checkProxyConnectResponse (res, out msg))
+ throw new WebSocketException (msg);
+ }
+ else {
+ _tcpClient = createTcpClient (_uri.DnsSafeHost, _uri.Port);
+ _stream = _tcpClient.GetStream ();
+ }
+
+ if (_isSecure) {
+ var conf = getSslConfiguration ();
+ var host = conf.TargetHost;
+
+ if (host != _uri.DnsSafeHost) {
+ var msg = "An invalid host name is specified.";
+
+ throw new WebSocketException (
+ CloseStatusCode.TlsHandshakeFailure,
+ msg
+ );
+ }
+
+ try {
+ var sslStream = new SslStream (
+ _stream,
+ false,
+ conf.ServerCertificateValidationCallback,
+ conf.ClientCertificateSelectionCallback
+ );
+
+ sslStream.AuthenticateAsClient (
+ host,
+ conf.ClientCertificates,
+ conf.EnabledSslProtocols,
+ conf.CheckCertificateRevocation
+ );
+
+ _stream = sslStream;
+ }
+ catch (Exception ex) {
+ throw new WebSocketException (
+ CloseStatusCode.TlsHandshakeFailure,
+ ex
+ );
+ }
+ }
+ }
+
+ private void startReceiving ()
+ {
+ if (_messageEventQueue.Count > 0)
+ _messageEventQueue.Clear ();
+
+ _pongReceived = new ManualResetEvent (false);
+ _receivingExited = new ManualResetEvent (false);
+
+ Action receive = null;
+ receive =
+ () => WebSocketFrame.ReadFrameAsync (
+ _stream,
+ false,
+ frame => {
+ var doNext = processReceivedFrame (frame)
+ && _readyState != WebSocketState.Closed;
+
+ if (!doNext) {
+ var exited = _receivingExited;
+
+ if (exited != null)
+ exited.Set ();
+
+ return;
+ }
+
+ receive ();
+
+ if (_inMessage)
+ return;
+
+ message ();
+ },
+ ex => {
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ abort ("An exception has occurred while receiving.", ex);
+ }
+ );
+
+ receive ();
+ }
+
+ // As client
+ private bool validateSecWebSocketExtensionsServerHeader (string value)
+ {
+ if (!_extensionsRequested)
+ return false;
+
+ if (value.Length == 0)
+ return false;
+
+ var compRequested = _compression != CompressionMethod.None;
+
+ foreach (var elm in value.SplitHeaderValue (',')) {
+ var ext = elm.Trim ();
+
+ if (compRequested && ext.IsCompressionExtension (_compression)) {
+ var param1 = "server_no_context_takeover";
+ var param2 = "client_no_context_takeover";
+
+ if (!ext.Contains (param1)) {
+ // The server did not send back "server_no_context_takeover".
+
+ return false;
+ }
+
+ var name = _compression.ToExtensionString ();
+
+ var isInvalid = ext.SplitHeaderValue (';').Contains (
+ t => {
+ t = t.Trim ();
+
+ var isValid = t == name
+ || t == param1
+ || t == param2;
+
+ return !isValid;
+ }
+ );
+
+ if (isInvalid)
+ return false;
+
+ compRequested = false;
+ }
+ else {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ // As server
+ internal void Accept ()
+ {
+ var accepted = accept ();
+
+ if (!accepted)
+ return;
+
+ open ();
+ }
+
+ // As server
+ internal void AcceptAsync ()
+ {
+ Func acceptor = accept;
+
+ acceptor.BeginInvoke (
+ ar => {
+ var accepted = acceptor.EndInvoke (ar);
+
+ if (!accepted)
+ return;
+
+ open ();
+ },
+ null
+ );
+ }
+
+ // As server
+ internal void Close (PayloadData payloadData, byte[] rawFrame)
+ {
+ lock (_forState) {
+ if (_readyState == WebSocketState.Closing) {
+ _log.Trace ("The close process is already in progress.");
+
+ return;
+ }
+
+ if (_readyState == WebSocketState.Closed) {
+ _log.Trace ("The connection has already been closed.");
+
+ return;
+ }
+
+ _readyState = WebSocketState.Closing;
+ }
+
+ _log.Trace ("Begin closing the connection.");
+
+ var sent = rawFrame != null && sendBytes (rawFrame);
+ var received = sent && _receivingExited != null
+ ? _receivingExited.WaitOne (_waitTime)
+ : false;
+
+ var res = sent && received;
+
+ var msg = String.Format (
+ "The closing was clean? {0} (sent: {1} received: {2})",
+ res,
+ sent,
+ received
+ );
+
+ _log.Debug (msg);
+
+ releaseServerResources ();
+ releaseCommonResources ();
+
+ _log.Trace ("End closing the connection.");
+
+ _readyState = WebSocketState.Closed;
+
+ var e = new CloseEventArgs (payloadData, res);
+
+ try {
+ OnClose.Emit (this, e);
+ }
+ catch (Exception ex) {
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
+ }
+ }
+
+ // As client
+ internal static string CreateBase64Key ()
+ {
+ var key = new byte[16];
+
+ RandomNumber.GetBytes (key);
+
+ return Convert.ToBase64String (key);
+ }
+
+ internal static string CreateResponseKey (string base64Key)
+ {
+ SHA1 sha1 = new SHA1CryptoServiceProvider ();
+
+ var src = base64Key + _guid;
+ var bytes = src.GetUTF8EncodedBytes ();
+ var key = sha1.ComputeHash (bytes);
+
+ return Convert.ToBase64String (key);
+ }
+
+ // As server
+ internal bool Ping (byte[] rawFrame)
+ {
+ if (_readyState != WebSocketState.Open)
+ return false;
+
+ var received = _pongReceived;
+
+ if (received == null)
+ return false;
+
+ lock (_forPing) {
+ try {
+ received.Reset ();
+
+ var sent = send (rawFrame);
+
+ if (!sent)
+ return false;
+
+ return received.WaitOne (_waitTime);
+ }
+ catch (ObjectDisposedException) {
+ return false;
+ }
+ }
+ }
+
+ // As server
+ internal void Send (
+ Opcode opcode,
+ byte[] data,
+ Dictionary cache
+ )
+ {
+ lock (_forSend) {
+ byte[] found;
+
+ if (!cache.TryGetValue (_compression, out found)) {
+ found = new WebSocketFrame (
+ Fin.Final,
+ opcode,
+ data.Compress (_compression),
+ _compression != CompressionMethod.None,
+ false
+ )
+ .ToArray ();
+
+ cache.Add (_compression, found);
+ }
+
+ send (found);
+ }
+ }
+
+ // As server
+ internal void Send (
+ Opcode opcode,
+ Stream sourceStream,
+ Dictionary cache
+ )
+ {
+ lock (_forSend) {
+ Stream found;
+
+ if (!cache.TryGetValue (_compression, out found)) {
+ found = sourceStream.Compress (_compression);
+
+ cache.Add (_compression, found);
+ }
+ else {
+ found.Position = 0;
+ }
+
+ send (opcode, found, _compression != CompressionMethod.None);
+ }
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///