Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Subprotocol Support

Adds subprotocol support to close issue #45. Allows you to set a
SubProtocol string array on the server and client. Conforms to RFC6455
spec; client closes if subprotocol is invalid, and chooses based on
priority from first listed. Tested with chrome subprotocols.
  • Loading branch information...
commit 7fd61690158f27cc57a28efb1bdaa423aa2213f7 1 parent d50174a
@ajacksified ajacksified authored
View
5 Alchemy.sln.DotSettings.user
@@ -0,0 +1,5 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+ <s:String x:Key="/Default/CodeInspection/Highlighting/AnalysisEnabled/@EntryValue">SOLUTION</s:String>
+ <s:Boolean x:Key="/Default/Housekeeping/UnitTestingMru/UnitTestSessionPersistentData/=A8BEAF6DE74F4EC5BDE42EBCE8C19510/@KeyIndexDefined">True</s:Boolean>
+ <s:String x:Key="/Default/Housekeeping/UnitTestingMru/UnitTestSessionPersistentData/=A8BEAF6DE74F4EC5BDE42EBCE8C19510/Name/@EntryValue">ClientSendDataConcurrent</s:String>
+ <s:String x:Key="/Default/Housekeeping/UnitTestingMru/UnitTestSessionPersistentData/=A8BEAF6DE74F4EC5BDE42EBCE8C19510/XmlSerializedElements/@EntryValue">&lt;Session&gt;&lt;Elements&gt;&lt;UnitTestElement Provider="nUnit" Id="Alchemy.ClientServer" type="NUnitTestFixtureElement" Project="D7B6AB15-5986-4FDC-ADA8-9EF14DF8F26D" /&gt;&lt;UnitTestElement Provider="nUnit" Id="Alchemy.ClientServerSubProtocol" type="NUnitTestFixtureElement" Project="D7B6AB15-5986-4FDC-ADA8-9EF14DF8F26D" /&gt;&lt;UnitTestElement Provider="nUnit" Id="Alchemy.Handlers.WebSocket.hybi00.DataFrameTest" type="NUnitTestFixtureElement" Project="D62E2F92-A930-44D9-90FB-864CAF837BAE" /&gt;&lt;UnitTestElement Provider="nUnit" Id="Alchemy.Handlers.WebSocket.rfc6455.DataFrameTest" type="NUnitTestFixtureElement" Project="D62E2F92-A930-44D9-90FB-864CAF837BAE" /&gt;&lt;UnitTestElement Provider="nUnit" Id="Alchemy.Handlers.WebSocket.hybi00.DataFrameTest.BackAndForthBig" ParentId="Alchemy.Handlers.WebSocket.hybi00.DataFrameTest" type="NUnitTestElement" TypeName="Alchemy.Handlers.WebSocket.hybi00.DataFrameTest" MethodName="BackAndForthBig" Project="D62E2F92-A930-44D9-90FB-864CAF837BAE" /&gt;&lt;UnitTestElement Provider="nUnit" Id="Alchemy.Handlers.WebSocket.rfc6455.DataFrameTest.BackAndForthBig" ParentId="Alchemy.Handlers.WebSocket.rfc6455.DataFrameTest" type="NUnitTestElement" TypeName="Alchemy.Handlers.WebSocket.rfc6455.DataFrameTest" MethodName="BackAndForthBig" Project="D62E2F92-A930-44D9-90FB-864CAF837BAE" /&gt;&lt;UnitTestElement Provider="nUnit" Id="Alchemy.Handlers.WebSocket.rfc6455.DataFrameTest.BackAndForthMedium" ParentId="Alchemy.Handlers.WebSocket.rfc6455.DataFrameTest" type="NUnitTestElement" TypeName="Alchemy.Handlers.WebSocket.rfc6455.DataFrameTest" MethodName="BackAndForthMedium" Project="D62E2F92-A930-44D9-90FB-864CAF837BAE" /&gt;&lt;UnitTestElement Provider="nUnit" Id="Alchemy.Handlers.WebSocket.hybi00.DataFrameTest.BackAndForthMedium" ParentId="Alchemy.Handlers.WebSocket.hybi00.DataFrameTest" type="NUnitTestElement" TypeName="Alchemy.Handlers.WebSocket.hybi00.DataFrameTest" MethodName="BackAndForthMedium" Project="D62E2F92-A930-44D9-90FB-864CAF837BAE" /&gt;&lt;UnitTestElement Provider="nUnit" Id="Alchemy.Handlers.WebSocket.rfc6455.DataFrameTest.BackAndForthSmall" ParentId="Alchemy.Handlers.WebSocket.rfc6455.DataFrameTest" type="NUnitTestElement" TypeName="Alchemy.Handlers.WebSocket.rfc6455.DataFrameTest" MethodName="BackAndForthSmall" Project="D62E2F92-A930-44D9-90FB-864CAF837BAE" /&gt;&lt;UnitTestElement Provider="nUnit" Id="Alchemy.Handlers.WebSocket.hybi00.DataFrameTest.BackAndForthSmall" ParentId="Alchemy.Handlers.WebSocket.hybi00.DataFrameTest" type="NUnitTestElement" TypeName="Alchemy.Handlers.WebSocket.hybi00.DataFrameTest" MethodName="BackAndForthSmall" Project="D62E2F92-A930-44D9-90FB-864CAF837BAE" /&gt;&lt;UnitTestElement Provider="nUnit" Id="Alchemy.ClientServer.ClientConnect" ParentId="Alchemy.ClientServer" type="NUnitTestElement" TypeName="Alchemy.ClientServer" MethodName="ClientConnect" Project="D7B6AB15-5986-4FDC-ADA8-9EF14DF8F26D" /&gt;&lt;UnitTestElement Provider="nUnit" Id="Alchemy.ClientServer.ClientSendData" ParentId="Alchemy.ClientServer" type="NUnitTestElement" TypeName="Alchemy.ClientServer" MethodName="ClientSendData" Project="D7B6AB15-5986-4FDC-ADA8-9EF14DF8F26D" /&gt;&lt;UnitTestElement Provider="nUnit" Id="Alchemy.ClientServer.ClientSendDataConcurrent" ParentId="Alchemy.ClientServer" type="NUnitTestElement" TypeName="Alchemy.ClientServer" MethodName="ClientSendDataConcurrent" Project="D7B6AB15-5986-4FDC-ADA8-9EF14DF8F26D" /&gt;&lt;UnitTestElement Provider="nUnit" Id="Alchemy.ClientServerSubProtocol.ClientShoulConnectWithSecondaryValidProtocol" ParentId="Alchemy.ClientServerSubProtocol" type="NUnitTestElement" TypeName="Alchemy.ClientServerSubProtocol" MethodName="ClientShoulConnectWithSecondaryValidProtocol" Project="D7B6AB15-5986-4FDC-ADA8-9EF14DF8F26D" /&gt;&lt;UnitTestElement Provider="nUnit" Id="Alchemy.ClientServerSubProtocol.ClientShoulConnectWithValidProtocol" ParentId="Alchemy.ClientServerSubProtocol" type="NUnitTestElement" TypeName="Alchemy.ClientServerSubProtocol" MethodName="ClientShoulConnectWithValidProtocol" Project="D7B6AB15-5986-4FDC-ADA8-9EF14DF8F26D" /&gt;&lt;UnitTestElement Provider="nUnit" Id="Alchemy.ClientServerSubProtocol.ClientShouldNotConnectWithInvalidProtocol" ParentId="Alchemy.ClientServerSubProtocol" type="NUnitTestElement" TypeName="Alchemy.ClientServerSubProtocol" MethodName="ClientShouldNotConnectWithInvalidProtocol" Project="D7B6AB15-5986-4FDC-ADA8-9EF14DF8F26D" /&gt;&lt;/Elements&gt;&lt;/Session&gt;</s:String></wpf:ResourceDictionary>
View
204 src/Alchemy/Alchemy.csproj
@@ -1,103 +1,103 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <PropertyGroup>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
- <ProductVersion>8.0.30703</ProductVersion>
- <SchemaVersion>2.0</SchemaVersion>
- <ProjectGuid>{45486CDE-86A3-4769-952F-E0821BF79493}</ProjectGuid>
- <OutputType>Library</OutputType>
- <AppDesignerFolder>Properties</AppDesignerFolder>
- <RootNamespace>Alchemy</RootNamespace>
- <AssemblyName>Alchemy</AssemblyName>
- <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
- <FileAlignment>512</FileAlignment>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <DebugSymbols>true</DebugSymbols>
- <DebugType>full</DebugType>
- <Optimize>false</Optimize>
- <OutputPath>bin\Debug\</OutputPath>
- <DefineConstants>DEBUG;TRACE</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <DebugType>pdbonly</DebugType>
- <Optimize>true</Optimize>
- <OutputPath>bin\Release\</OutputPath>
- <DefineConstants>TRACE</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- </PropertyGroup>
- <PropertyGroup>
- <StartupObject />
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
- <DebugSymbols>true</DebugSymbols>
- <OutputPath>bin\x64\Debug\</OutputPath>
- <DefineConstants>DEBUG;TRACE</DefineConstants>
- <DebugType>full</DebugType>
- <PlatformTarget>x64</PlatformTarget>
- <ErrorReport>prompt</ErrorReport>
- <CodeAnalysisIgnoreBuiltInRuleSets>true</CodeAnalysisIgnoreBuiltInRuleSets>
- <CodeAnalysisIgnoreBuiltInRules>true</CodeAnalysisIgnoreBuiltInRules>
- <CodeAnalysisFailOnMissingRules>false</CodeAnalysisFailOnMissingRules>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
- <OutputPath>bin\x64\Release\</OutputPath>
- <DefineConstants>TRACE</DefineConstants>
- <Optimize>true</Optimize>
- <DebugType>pdbonly</DebugType>
- <PlatformTarget>AnyCPU</PlatformTarget>
- <ErrorReport>prompt</ErrorReport>
- <CodeAnalysisIgnoreBuiltInRuleSets>true</CodeAnalysisIgnoreBuiltInRuleSets>
- <CodeAnalysisIgnoreBuiltInRules>true</CodeAnalysisIgnoreBuiltInRules>
- <CodeAnalysisFailOnMissingRules>false</CodeAnalysisFailOnMissingRules>
- </PropertyGroup>
- <ItemGroup>
- <Reference Include="System" />
- <Reference Include="System.Web" />
- <Reference Include="System.XML" />
- </ItemGroup>
- <ItemGroup>
- <Compile Include="AccessPolicyServer.cs" />
- <Compile Include="Classes\Context.cs" />
- <Compile Include="Handlers\Authentication.cs" />
- <Compile Include="Handlers\IAuthentication.cs" />
- <Compile Include="Handlers\WebSocket\DataFrame.cs" />
- <Compile Include="Handlers\WebSocket\hybi00\DataFrame.cs" />
- <Compile Include="Handlers\WebSocket\hybi10\DataFrame.cs" />
- <Compile Include="Classes\Header.cs" />
- <Compile Include="Classes\Response.cs" />
- <Compile Include="Classes\UserContext.cs" />
- <Compile Include="Handlers\Handler.cs" />
- <Compile Include="Handlers\WebSocket\hybi00\Handler.cs" />
- <Compile Include="Handlers\WebSocket\hybi00\Handshakes.cs" />
- <Compile Include="Handlers\WebSocket\hybi00\Authentication.cs" />
- <Compile Include="Handlers\WebSocket\hybi10\FrameHeader.cs" />
- <Compile Include="Handlers\WebSocket\hybi10\Authentication.cs" />
- <Compile Include="Handlers\WebSocket\hybi10\Handshakes.cs" />
- <Compile Include="Handlers\WebSocket\hybi10\Handler.cs" />
- <Compile Include="Handlers\WebSocket\WebSocketHandler.cs" />
- <Compile Include="TcpServer.cs" />
- <Compile Include="WebSocketClient.cs" />
- <Compile Include="WebSocketServer.cs" />
- <Compile Include="Properties\AssemblyInfo.cs" />
- </ItemGroup>
- <ItemGroup>
- <None Include="Alchemy.config">
- <CopyToOutputDirectory>Always</CopyToOutputDirectory>
- <SubType>Designer</SubType>
- </None>
- </ItemGroup>
- <ItemGroup />
- <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
- <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
- Other similar extension points exist, see Microsoft.Common.targets.
- <Target Name="BeforeBuild">
- </Target>
- <Target Name="AfterBuild">
- </Target>
- -->
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>8.0.30703</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{45486CDE-86A3-4769-952F-E0821BF79493}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Alchemy</RootNamespace>
+ <AssemblyName>Alchemy</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup>
+ <StartupObject />
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+ <DebugSymbols>true</DebugSymbols>
+ <OutputPath>bin\x64\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <DebugType>full</DebugType>
+ <PlatformTarget>x64</PlatformTarget>
+ <ErrorReport>prompt</ErrorReport>
+ <CodeAnalysisIgnoreBuiltInRuleSets>true</CodeAnalysisIgnoreBuiltInRuleSets>
+ <CodeAnalysisIgnoreBuiltInRules>true</CodeAnalysisIgnoreBuiltInRules>
+ <CodeAnalysisFailOnMissingRules>false</CodeAnalysisFailOnMissingRules>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+ <OutputPath>bin\x64\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <Optimize>true</Optimize>
+ <DebugType>pdbonly</DebugType>
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <ErrorReport>prompt</ErrorReport>
+ <CodeAnalysisIgnoreBuiltInRuleSets>true</CodeAnalysisIgnoreBuiltInRuleSets>
+ <CodeAnalysisIgnoreBuiltInRules>true</CodeAnalysisIgnoreBuiltInRules>
+ <CodeAnalysisFailOnMissingRules>false</CodeAnalysisFailOnMissingRules>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Web" />
+ <Reference Include="System.XML" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="AccessPolicyServer.cs" />
+ <Compile Include="Classes\Context.cs" />
+ <Compile Include="Handlers\Authentication.cs" />
+ <Compile Include="Handlers\IAuthentication.cs" />
+ <Compile Include="Handlers\WebSocket\DataFrame.cs" />
+ <Compile Include="Handlers\WebSocket\hybi00\DataFrame.cs" />
+ <Compile Include="Handlers\WebSocket\rfc6455\DataFrame.cs" />
+ <Compile Include="Classes\Header.cs" />
+ <Compile Include="Classes\Response.cs" />
+ <Compile Include="Classes\UserContext.cs" />
+ <Compile Include="Handlers\Handler.cs" />
+ <Compile Include="Handlers\WebSocket\hybi00\Handler.cs" />
+ <Compile Include="Handlers\WebSocket\hybi00\Handshakes.cs" />
+ <Compile Include="Handlers\WebSocket\hybi00\Authentication.cs" />
+ <Compile Include="Handlers\WebSocket\rfc6455\FrameHeader.cs" />
+ <Compile Include="Handlers\WebSocket\rfc6455\Authentication.cs" />
+ <Compile Include="Handlers\WebSocket\rfc6455\Handshakes.cs" />
+ <Compile Include="Handlers\WebSocket\rfc6455\Handler.cs" />
+ <Compile Include="Handlers\WebSocket\WebSocketHandler.cs" />
+ <Compile Include="TcpServer.cs" />
+ <Compile Include="WebSocketClient.cs" />
+ <Compile Include="WebSocketServer.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="Alchemy.config">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ <SubType>Designer</SubType>
+ </None>
+ </ItemGroup>
+ <ItemGroup />
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
</Project>
View
284 src/Alchemy/Classes/Header.cs
@@ -1,136 +1,150 @@
-using System;
-using System.Collections.Specialized;
-using System.Text.RegularExpressions;
-using System.Web;
-
-namespace Alchemy.Classes
-{
- /// <summary>
- /// What protocols we support
- /// </summary>
- public enum Protocol
- {
- None = -1,
- WebSocketHybi10 = 0,
- WebSocketHybi00 = 1
- }
-
- /// <summary>
- /// This class implements a rudimentary HTTP header reading interface.
- /// </summary>
- public class Header
- {
- /// <summary>
- /// Regular expression to parse http header
- /// </summary>
- public static string Pattern =
- @"^(?<connect>[^\s]+)?\s?(?<path>[^\s]+)?\s?HTTP\/1\.1(.*?)?\r\n" + // HTTP Request
- @"((?<field_name>[^:\r\n]+):(?<field_value>[^\r\n]+)\r\n)+";
-
- // HTTP Header Fields (<Field_Name>: <Field_Value> CR LF)
-
- /// <summary>
- /// A collection of fields attached to the header.
- /// </summary>
- private readonly NameValueCollection _fields = new NameValueCollection();
-
- /// <summary>
- /// Any cookies sent with the header.
- /// </summary>
- public HttpCookieCollection Cookies = new HttpCookieCollection();
-
- /// <summary>
- /// The HTTP Method (GET/POST/PUT, etc.)
- /// </summary>
- public String Method = String.Empty;
-
- /// <summary>
- /// What protocol this header represents, if any.
- /// </summary>
- public Protocol Protocol = Protocol.None;
-
-
- /// <summary>
- /// The path requested by the header.
- /// </summary>
- public string RequestPath = string.Empty;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="Header"/> class.
- /// Accepts a string that represents an HTTP header.
- /// </summary>
- /// <param name="data">The data.</param>
- public Header(string data)
- {
- // Parse HTTP Header
- var regex = new Regex(Pattern, RegexOptions.IgnoreCase);
- Match match = regex.Match(data);
- GroupCollection someFields = match.Groups;
- Group fieldNameCollection = someFields["field_name"];
- Group fieldValueCollection = someFields["field_value"];
- if (fieldNameCollection != null && fieldValueCollection != null)
- {
- // run through every match and save them in the handshake object
- for (int i = 0; i < fieldNameCollection.Captures.Count; i++)
- {
- string name = fieldNameCollection.Captures[i].ToString().ToLower();
- string value = fieldValueCollection.Captures[i].ToString().Trim();
- switch (name)
- {
- case "cookie":
- string[] cookieArray = value.Split(';');
- foreach (string cookie in cookieArray)
- {
- int cookieIndex = cookie.IndexOf('=');
- if (cookieIndex >= 0)
- {
- string cookieName = cookie.Remove(cookieIndex).TrimStart();
- string cookieValue = cookie.Substring(cookieIndex + 1);
- if (cookieName != string.Empty)
- {
- Cookies.Add(new HttpCookie(cookieName, cookieValue));
- }
- }
- }
- break;
- default:
- _fields.Add(name, value);
- break;
- }
- }
- }
-
- Group pathCollection = someFields["path"];
- Group methodCollection = someFields["connect"];
-
- if (pathCollection != null)
- {
- if (pathCollection.Captures.Count > 0)
- {
- RequestPath = pathCollection.Captures[0].Value.Trim();
- }
- }
- if (methodCollection != null)
- {
- if (methodCollection.Captures.Count > 0)
- {
- Method = methodCollection.Captures[0].Value.Trim();
- }
- }
-
- int version;
- Int32.TryParse(_fields["sec-websocket-version"], out version);
-
- Protocol = version < 8 ? Protocol.WebSocketHybi00 : Protocol.WebSocketHybi10;
- }
-
- /// <summary>
- /// Gets or sets the Fields object with the specified key.
- /// </summary>
- public string this[string key]
- {
- get { return _fields[key]; }
- set { _fields[key] = value; }
- }
- }
+using System;
+using System.Collections.Specialized;
+using System.Text.RegularExpressions;
+using System.Web;
+
+namespace Alchemy.Classes
+{
+ /// <summary>
+ /// What protocols we support
+ /// </summary>
+ public enum Protocol
+ {
+ None = -1,
+ WebSocketRFC6455 = 0,
+ WebSocketHybi00 = 1
+ }
+
+ /// <summary>
+ /// This class implements a rudimentary HTTP header reading interface.
+ /// </summary>
+ public class Header
+ {
+ /// <summary>
+ /// Regular expression to parse http header
+ /// </summary>
+ public static string Pattern =
+ @"^(?<connect>[^\s]+)?\s?(?<path>[^\s]+)?\s?HTTP\/1\.1(.*?)?\r\n" + // HTTP Request
+ @"((?<field_name>[^:\r\n]+):(?<field_value>[^\r\n]+)\r\n)+";
+
+ // HTTP Header Fields (<Field_Name>: <Field_Value> CR LF)
+
+ /// <summary>
+ /// A collection of fields attached to the header.
+ /// </summary>
+ private readonly NameValueCollection _fields = new NameValueCollection();
+
+ /// <summary>
+ /// Any cookies sent with the header.
+ /// </summary>
+ public HttpCookieCollection Cookies = new HttpCookieCollection();
+
+ /// <summary>
+ /// The HTTP Method (GET/POST/PUT, etc.)
+ /// </summary>
+ public String Method = String.Empty;
+
+ /// <summary>
+ /// What protocol this header represents, if any.
+ /// </summary>
+ public Protocol Protocol = Protocol.None;
+
+
+ /// <summary>
+ /// The path requested by the header.
+ /// </summary>
+ public string RequestPath = string.Empty;
+
+ /// <summary>
+ /// The subprotocols specified by the header.
+ /// </summary>
+ public string[] SubProtocols;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Header"/> class.
+ /// Accepts a string that represents an HTTP header.
+ /// </summary>
+ /// <param name="data">The data.</param>
+ public Header(string data)
+ {
+ // Parse HTTP Header
+ var regex = new Regex(Pattern, RegexOptions.IgnoreCase);
+ var match = regex.Match(data);
+ var matchGroups = match.Groups;
+ var fieldNameCollection = matchGroups["field_name"];
+ var fieldValueCollection = matchGroups["field_value"];
+
+ if (fieldNameCollection != null && fieldValueCollection != null)
+ {
+ // run through every match and save them in the handshake object
+ for (var i = 0; i < fieldNameCollection.Captures.Count; i++)
+ {
+ var name = fieldNameCollection.Captures[i].ToString().ToLower();
+ var value = fieldValueCollection.Captures[i].ToString().Trim();
+
+ switch (name)
+ {
+ case "cookie":
+ var cookieArray = value.Split(';');
+ foreach (var cookie in cookieArray)
+ {
+ var cookieIndex = cookie.IndexOf('=');
+
+ if (cookieIndex < 0) continue;
+
+ var cookieName = cookie.Remove(cookieIndex).TrimStart();
+
+ var cookieValue = cookie.Substring(cookieIndex + 1);
+ if (cookieName != string.Empty)
+ {
+ Cookies.Add(new HttpCookie(cookieName, cookieValue));
+ }
+ }
+ break;
+ default:
+ _fields.Add(name, value);
+ break;
+ }
+ }
+ }
+
+ var pathCollection = matchGroups["path"];
+ var methodCollection = matchGroups["connect"];
+
+ if (pathCollection != null)
+ {
+ if (pathCollection.Captures.Count > 0)
+ {
+ RequestPath = pathCollection.Captures[0].Value.Trim();
+ }
+ }
+
+ if (methodCollection != null)
+ {
+ if (methodCollection.Captures.Count > 0)
+ {
+ Method = methodCollection.Captures[0].Value.Trim();
+ }
+ }
+
+ int version;
+ Int32.TryParse(_fields["sec-websocket-version"], out version);
+
+ if(!String.IsNullOrEmpty(_fields["sec-websocket-protocol"]))
+ {
+ SubProtocols = _fields["sec-websocket-protocol"].Split(',');
+ }
+
+ Protocol = version < 8 ? Protocol.WebSocketHybi00 : Protocol.WebSocketRFC6455;
+ }
+
+ /// <summary>
+ /// Gets or sets the Fields object with the specified key.
+ /// </summary>
+ public string this[string key]
+ {
+ get { return _fields[key]; }
+ set { _fields[key] = value; }
+ }
+ }
}
View
400 src/Alchemy/Classes/UserContext.cs
@@ -1,202 +1,202 @@
-using System;
-using System.Net;
-using Alchemy.Handlers.WebSocket;
-
-namespace Alchemy.Classes
-{
- /// <summary>
- /// Contains data we will export to the Event Delegates.
- /// </summary>
- public class UserContext
- {
- /// <summary>
- /// AQ Link to the parent User Context
- /// </summary>
- private readonly Context _context;
-
- /// <summary>
- /// The remote endpoint address.
- /// </summary>
- public EndPoint ClientAddress;
-
- /// <summary>
- /// User defined data. Can be anything.
- /// </summary>
- public Object Data;
-
- /// <summary>
- /// The data Frame that this client is currently processing.
- /// </summary>
- public DataFrame DataFrame;
-
- /// <summary>
- /// OnEvent Delegates specific to this connection.
- /// </summary>
- protected OnEventDelegate OnConnectDelegate = x => { };
-
- protected OnEventDelegate OnConnectedDelegate = x => { };
- protected OnEventDelegate OnDisconnectDelegate = x => { };
- protected OnEventDelegate OnReceiveDelegate = x => { };
- protected OnEventDelegate OnSendDelegate = x => { };
-
- /// <summary>
- /// The type of connection this is
- /// </summary>
- public Protocol Protocol = Protocol.None;
-
- /// <summary>
- /// The path of this request.
- /// </summary>
- public string RequestPath = "/";
-
- /// <summary>
- /// Initializes a new instance of the <see cref="UserContext"/> class.
- /// </summary>
- /// <param name="context">The user context.</param>
- public UserContext(Context context)
- {
- _context = context;
- }
-
- /// <summary>
- /// The internal context connection header
- /// </summary>
- public Header Header
- {
- get { return _context.Header; }
- }
-
- /// <summary>
- /// The maximum frame size
- /// </summary>
- public UInt64 MaxFrameSize
- {
- get { return _context.MaxFrameSize; }
- set { _context.MaxFrameSize = value; }
- }
-
- /// <summary>
- /// Called when [connect].
- /// </summary>
- public void OnConnect()
- {
- OnConnectDelegate(this);
- }
-
- /// <summary>
- /// Called when [connected].
- /// </summary>
- public void OnConnected()
- {
- OnConnectedDelegate(this);
- }
-
- /// <summary>
- /// Called when [disconnect].
- /// </summary>
- public void OnDisconnect()
- {
- _context.Connected = false;
- OnDisconnectDelegate(this);
- }
-
- /// <summary>
- /// Called when [send].
- /// </summary>
- public void OnSend()
- {
- OnSendDelegate(this);
- }
-
- /// <summary>
- /// Called when [receive].
- /// </summary>
- public void OnReceive()
- {
- OnReceiveDelegate(this);
- }
-
- /// <summary>
- /// Sets the on connect event.
- /// </summary>
- /// <param name="aDelegate">The Event Delegate.</param>
- public void SetOnConnect(OnEventDelegate aDelegate)
- {
- OnConnectDelegate = aDelegate;
- }
-
- /// <summary>
- /// Sets the on connected event.
- /// </summary>
- /// <param name="aDelegate">The Event Delegate.</param>
- public void SetOnConnected(OnEventDelegate aDelegate)
- {
- OnConnectedDelegate = aDelegate;
- }
-
- /// <summary>
- /// Sets the on disconnect event.
- /// </summary>
- /// <param name="aDelegate">The Event Delegate.</param>
- public void SetOnDisconnect(OnEventDelegate aDelegate)
- {
- OnDisconnectDelegate = aDelegate;
- }
-
- /// <summary>
- /// Sets the on send event.
- /// </summary>
- /// <param name="aDelegate">The Event Delegate.</param>
- public void SetOnSend(OnEventDelegate aDelegate)
- {
- OnSendDelegate = aDelegate;
- }
-
- /// <summary>
- /// Sets the on receive event.
- /// </summary>
- /// <param name="aDelegate">The Event Delegate.</param>
- public void SetOnReceive(OnEventDelegate aDelegate)
- {
- OnReceiveDelegate = aDelegate;
- }
-
- /// <summary>
- /// Sends the specified data.
- /// </summary>
- /// <param name="dataFrame">The data.</param>
- /// <param name="raw">Whether or not to send raw data</param>
- /// <param name="close">if set to <c>true</c> [close].</param>
- public void Send(DataFrame dataFrame, bool raw = false, bool close = false)
- {
- _context.Handler.Send(dataFrame, _context, raw, close);
- }
-
- /// <summary>
- /// Sends the specified data.
- /// </summary>
- /// <param name="aString">The data.</param>
- /// <param name="raw">whether or not to send raw data</param>
- /// <param name="close">if set to <c>true</c> [close].</param>
- public void Send(String aString, bool raw = false, bool close = false)
- {
- DataFrame dataFrame = DataFrame.CreateInstance();
- dataFrame.Append(aString);
- _context.Handler.Send(dataFrame, _context, raw, close);
- }
-
- /// <summary>
- /// Sends the specified data.
- /// </summary>
- /// <param name="someBytes">The data.</param>
- /// <param name="raw">whether or not to send raw data</param>
- /// <param name="close">if set to <c>true</c> [close].</param>
- public void Send(byte[] someBytes, bool raw = false, bool close = false)
- {
+using System;
+using System.Net;
+using Alchemy.Handlers.WebSocket;
+
+namespace Alchemy.Classes
+{
+ /// <summary>
+ /// Contains data we will export to the Event Delegates.
+ /// </summary>
+ public class UserContext
+ {
+ /// <summary>
+ /// AQ Link to the parent User Context
+ /// </summary>
+ private readonly Context _context;
+
+ /// <summary>
+ /// The remote endpoint address.
+ /// </summary>
+ public EndPoint ClientAddress;
+
+ /// <summary>
+ /// User defined data. Can be anything.
+ /// </summary>
+ public Object Data;
+
+ /// <summary>
+ /// The data Frame that this client is currently processing.
+ /// </summary>
+ public DataFrame DataFrame;
+
+ /// <summary>
+ /// OnEvent Delegates specific to this connection.
+ /// </summary>
+ protected OnEventDelegate OnConnectDelegate = x => { };
+
+ protected OnEventDelegate OnConnectedDelegate = x => { };
+ protected OnEventDelegate OnDisconnectDelegate = x => { };
+ protected OnEventDelegate OnReceiveDelegate = x => { };
+ protected OnEventDelegate OnSendDelegate = x => { };
+
+ /// <summary>
+ /// The type of connection this is
+ /// </summary>
+ public Protocol Protocol = Protocol.None;
+
+ /// <summary>
+ /// The path of this request.
+ /// </summary>
+ public string RequestPath = "/";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UserContext"/> class.
+ /// </summary>
+ /// <param name="context">The user context.</param>
+ public UserContext(Context context)
+ {
+ _context = context;
+ }
+
+ /// <summary>
+ /// The internal context connection header
+ /// </summary>
+ public Header Header
+ {
+ get { return _context.Header; }
+ }
+
+ /// <summary>
+ /// The maximum frame size
+ /// </summary>
+ public UInt64 MaxFrameSize
+ {
+ get { return _context.MaxFrameSize; }
+ set { _context.MaxFrameSize = value; }
+ }
+
+ /// <summary>
+ /// Called when [connect].
+ /// </summary>
+ public void OnConnect()
+ {
+ OnConnectDelegate(this);
+ }
+
+ /// <summary>
+ /// Called when [connected].
+ /// </summary>
+ public void OnConnected()
+ {
+ OnConnectedDelegate(this);
+ }
+
+ /// <summary>
+ /// Called when [disconnect].
+ /// </summary>
+ public void OnDisconnect()
+ {
+ _context.Connected = false;
+ OnDisconnectDelegate(this);
+ }
+
+ /// <summary>
+ /// Called when [send].
+ /// </summary>
+ public void OnSend()
+ {
+ OnSendDelegate(this);
+ }
+
+ /// <summary>
+ /// Called when [receive].
+ /// </summary>
+ public void OnReceive()
+ {
+ OnReceiveDelegate(this);
+ }
+
+ /// <summary>
+ /// Sets the on connect event.
+ /// </summary>
+ /// <param name="aDelegate">The Event Delegate.</param>
+ public void SetOnConnect(OnEventDelegate aDelegate)
+ {
+ OnConnectDelegate = aDelegate;
+ }
+
+ /// <summary>
+ /// Sets the on connected event.
+ /// </summary>
+ /// <param name="aDelegate">The Event Delegate.</param>
+ public void SetOnConnected(OnEventDelegate aDelegate)
+ {
+ OnConnectedDelegate = aDelegate;
+ }
+
+ /// <summary>
+ /// Sets the on disconnect event.
+ /// </summary>
+ /// <param name="aDelegate">The Event Delegate.</param>
+ public void SetOnDisconnect(OnEventDelegate aDelegate)
+ {
+ OnDisconnectDelegate = aDelegate;
+ }
+
+ /// <summary>
+ /// Sets the on send event.
+ /// </summary>
+ /// <param name="aDelegate">The Event Delegate.</param>
+ public void SetOnSend(OnEventDelegate aDelegate)
+ {
+ OnSendDelegate = aDelegate;
+ }
+
+ /// <summary>
+ /// Sets the on receive event.
+ /// </summary>
+ /// <param name="aDelegate">The Event Delegate.</param>
+ public void SetOnReceive(OnEventDelegate aDelegate)
+ {
+ OnReceiveDelegate = aDelegate;
+ }
+
+ /// <summary>
+ /// Sends the specified data.
+ /// </summary>
+ /// <param name="dataFrame">The data.</param>
+ /// <param name="raw">Whether or not to send raw data</param>
+ /// <param name="close">if set to <c>true</c> [close].</param>
+ public void Send(DataFrame dataFrame, bool raw = false, bool close = false)
+ {
+ _context.Handler.Send(dataFrame, _context, raw, close);
+ }
+
+ /// <summary>
+ /// Sends the specified data.
+ /// </summary>
+ /// <param name="aString">The data.</param>
+ /// <param name="raw">whether or not to send raw data</param>
+ /// <param name="close">if set to <c>true</c> [close].</param>
+ public void Send(String aString, bool raw = false, bool close = false)
+ {
DataFrame dataFrame = DataFrame.CreateInstance();
- dataFrame.IsByte = true;
- dataFrame.Append(someBytes);
- _context.Handler.Send(dataFrame, _context, raw, close);
- }
- }
+ dataFrame.Append(aString);
+ _context.Handler.Send(dataFrame, _context, raw, close);
+ }
+
+ /// <summary>
+ /// Sends the specified data.
+ /// </summary>
+ /// <param name="someBytes">The data.</param>
+ /// <param name="raw">whether or not to send raw data</param>
+ /// <param name="close">if set to <c>true</c> [close].</param>
+ public void Send(byte[] someBytes, bool raw = false, bool close = false)
+ {
+ DataFrame dataFrame = DataFrame.CreateInstance();
+ dataFrame.IsByte = true;
+ dataFrame.Append(someBytes);
+ _context.Handler.Send(dataFrame, _context, raw, close);
+ }
+ }
}
View
316 src/Alchemy/Handlers/Handler.cs
@@ -1,159 +1,159 @@
-using System;
-using System.Collections.Generic;
-using System.Net.Sockets;
-using System.Text;
-using System.Threading;
-using Alchemy.Classes;
-using Alchemy.Handlers.WebSocket;
-
-namespace Alchemy.Handlers
-{
- /// <summary>
- /// When the protocol has not yet been determined the system defaults to this request handler.
- /// Singleton, just like the other handlers.
- /// </summary>
- public class Handler
- {
- private static Handler _instance;
-
- protected static SemaphoreSlim CreateLock = new SemaphoreSlim(1);
- internal IAuthentication Authentication;
- protected Handler() {}
-
- public static Handler Instance
- {
- get
- {
- if (_instance != null)
- {
- return _instance;
- }
- CreateLock.Wait();
- _instance = new Handler();
- CreateLock.Release();
- return _instance;
- }
- }
-
- /// <summary>
- /// Handles the initial request.
- /// Attempts to process the header that should have been sent.
- /// Otherwise, through magic and wizardry, the client gets disconnected.
- /// </summary>
- /// <param name="context">The user context.</param>
- public virtual void HandleRequest(Context context)
- {
- if (context.IsSetup)
- {
- context.Disconnect();
- }
- else
- {
- ProcessHeader(context);
- }
- }
-
- /// <summary>
- /// Processes the header.
- /// </summary>
- /// <param name="context">The user context.</param>
- public void ProcessHeader(Context context)
- {
- string data = Encoding.UTF8.GetString(context.Buffer, 0, context.ReceivedByteCount);
- //Check first to see if this is a flash socket XML request.
- if (data == "<policy-file-request/>\0")
- {
- //if it is, we access the Access Policy Server instance to send the appropriate response.
- context.Server.AccessPolicyServer.SendResponse(context.Connection);
- context.Disconnect();
- }
- else //If it isn't, process http/websocket header as normal.
- {
- context.Header = new Header(data);
- switch (context.Header.Protocol)
- {
- case Protocol.WebSocketHybi00:
- context.Handler = WebSocket.hybi00.Handler.Instance;
- context.UserContext.DataFrame = new WebSocket.hybi00.DataFrame();
- break;
- case Protocol.WebSocketHybi10:
- context.Handler = WebSocket.hybi10.Handler.Instance;
- context.UserContext.DataFrame = new WebSocket.hybi10.DataFrame();
- break;
- default:
- context.Header.Protocol = Protocol.None;
- break;
- }
- if (context.Header.Protocol != Protocol.None)
- {
- context.Handler.HandleRequest(context);
- }
- else
- {
- context.UserContext.Send(Response.NotImplemented, true, true);
- }
- }
- }
-
- /// <summary>
- /// Sends the specified data.
- /// </summary>
- /// <param name="dataFrame">The data.</param>
- /// <param name="context">The user context.</param>
- /// <param name="raw">whether or not to send raw data</param>
- /// <param name="close">if set to <c>true</c> [close].</param>
- public void Send(DataFrame dataFrame, Context context, bool raw = false, bool close = false)
- {
- if (context.Connected)
- {
- AsyncCallback callback = EndSend;
- if (close)
- {
- callback = EndSendAndClose;
- }
- context.SendReady.Wait();
- try
- {
- List<ArraySegment<byte>> data = raw ? dataFrame.AsRaw() : dataFrame.AsFrame();
- context.Connection.Client.BeginSend(data, SocketFlags.None,
- callback,
- context);
- }
- catch
- {
- context.Disconnect();
- }
- }
- }
-
- /// <summary>
- /// Ends the send.
- /// </summary>
- /// <param name="result">The Async result.</param>
- public void EndSend(IAsyncResult result)
- {
- var context = (Context) result.AsyncState;
- try
- {
- context.Connection.Client.EndSend(result);
- context.SendReady.Release();
- }
- catch
- {
- context.Disconnect();
- }
- context.UserContext.OnSend();
- }
-
- /// <summary>
- /// Ends the send and closes the connection.
- /// </summary>
- /// <param name="result">The Async result.</param>
- public void EndSendAndClose(IAsyncResult result)
- {
- var context = (Context) result.AsyncState;
- EndSend(result);
- context.Disconnect();
- }
- }
+using System;
+using System.Collections.Generic;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using Alchemy.Classes;
+using Alchemy.Handlers.WebSocket;
+
+namespace Alchemy.Handlers
+{
+ /// <summary>
+ /// When the protocol has not yet been determined the system defaults to this request handler.
+ /// Singleton, just like the other handlers.
+ /// </summary>
+ public class Handler
+ {
+ private static Handler _instance;
+
+ protected static SemaphoreSlim CreateLock = new SemaphoreSlim(1);
+ internal IAuthentication Authentication;
+ protected Handler() {}
+
+ public static Handler Instance
+ {
+ get
+ {
+ if (_instance != null)
+ {
+ return _instance;
+ }
+ CreateLock.Wait();
+ _instance = new Handler();
+ CreateLock.Release();
+ return _instance;
+ }
+ }
+
+ /// <summary>
+ /// Handles the initial request.
+ /// Attempts to process the header that should have been sent.
+ /// Otherwise, through magic and wizardry, the client gets disconnected.
+ /// </summary>
+ /// <param name="context">The user context.</param>
+ public virtual void HandleRequest(Context context)
+ {
+ if (context.IsSetup)
+ {
+ context.Disconnect();
+ }
+ else
+ {
+ ProcessHeader(context);
+ }
+ }
+
+ /// <summary>
+ /// Processes the header.
+ /// </summary>
+ /// <param name="context">The user context.</param>
+ public void ProcessHeader(Context context)
+ {
+ string data = Encoding.UTF8.GetString(context.Buffer, 0, context.ReceivedByteCount);
+ //Check first to see if this is a flash socket XML request.
+ if (data == "<policy-file-request/>\0")
+ {
+ //if it is, we access the Access Policy Server instance to send the appropriate response.
+ context.Server.AccessPolicyServer.SendResponse(context.Connection);
+ context.Disconnect();
+ }
+ else //If it isn't, process http/websocket header as normal.
+ {
+ context.Header = new Header(data);
+ switch (context.Header.Protocol)
+ {
+ case Protocol.WebSocketHybi00:
+ context.Handler = WebSocket.hybi00.Handler.Instance;
+ context.UserContext.DataFrame = new WebSocket.hybi00.DataFrame();
+ break;
+ case Protocol.WebSocketRFC6455:
+ context.Handler = WebSocket.rfc6455.Handler.Instance;
+ context.UserContext.DataFrame = new WebSocket.rfc6455.DataFrame();
+ break;
+ default:
+ context.Header.Protocol = Protocol.None;
+ break;
+ }
+ if (context.Header.Protocol != Protocol.None)
+ {
+ context.Handler.HandleRequest(context);
+ }
+ else
+ {
+ context.UserContext.Send(Response.NotImplemented, true, true);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Sends the specified data.
+ /// </summary>
+ /// <param name="dataFrame">The data.</param>
+ /// <param name="context">The user context.</param>
+ /// <param name="raw">whether or not to send raw data</param>
+ /// <param name="close">if set to <c>true</c> [close].</param>
+ public void Send(DataFrame dataFrame, Context context, bool raw = false, bool close = false)
+ {
+ if (context.Connected)
+ {
+ AsyncCallback callback = EndSend;
+ if (close)
+ {
+ callback = EndSendAndClose;
+ }
+ context.SendReady.Wait();
+ try
+ {
+ List<ArraySegment<byte>> data = raw ? dataFrame.AsRaw() : dataFrame.AsFrame();
+ context.Connection.Client.BeginSend(data, SocketFlags.None,
+ callback,
+ context);
+ }
+ catch
+ {
+ context.Disconnect();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Ends the send.
+ /// </summary>
+ /// <param name="result">The Async result.</param>
+ public void EndSend(IAsyncResult result)
+ {
+ var context = (Context) result.AsyncState;
+ try
+ {
+ context.Connection.Client.EndSend(result);
+ context.SendReady.Release();
+ }
+ catch
+ {
+ context.Disconnect();
+ }
+ context.UserContext.OnSend();
+ }
+
+ /// <summary>
+ /// Ends the send and closes the connection.
+ /// </summary>
+ /// <param name="result">The Async result.</param>
+ public void EndSendAndClose(IAsyncResult result)
+ {
+ var context = (Context) result.AsyncState;
+ EndSend(result);
+ context.Disconnect();
+ }
+ }
}
View
207 src/Alchemy/Handlers/WebSocket/hybi00/Authentication.cs
@@ -1,104 +1,105 @@
-using System;
-using System.Linq;
-using System.Security.Cryptography;
-using System.Text;
-using Alchemy.Classes;
-
-namespace Alchemy.Handlers.WebSocket.hybi00
-{
- /// <summary>
- /// Handles the handshaking between the client and the host, when a new connection is created
- /// </summary>
- internal class Authentication : Handlers.Authentication
- {
- protected override bool CheckAuthentication(Context context)
- {
- if (context.ReceivedByteCount > 8)
- {
- var handshake =
- new ClientHandshake(new ArraySegment<byte>(context.Buffer, context.ReceivedByteCount - 8, 8),
- context.Header);
- // See if our header had the required information
- if (handshake.IsValid())
- {
- // Optionally check Origin and Location if they're set.
- if (Origin != string.Empty)
- {
- if (handshake.Origin != "http://" + Origin)
- {
- return false;
- }
- }
- if (Destination != string.Empty)
- {
- if (handshake.Host != Destination + ":" + context.Server.Port.ToString())
- {
- return false;
- }
- }
- // Generate response handshake for the client
- ServerHandshake serverShake = GenerateResponseHandshake(handshake);
- // Send the response handshake
- SendServerHandshake(serverShake, context);
- return true;
- }
- }
- return false;
- }
-
- private static ServerHandshake GenerateResponseHandshake(ClientHandshake handshake)
- {
- var responseHandshake = new ServerHandshake
- {
- Location = "ws://" + handshake.Host + handshake.ResourcePath,
- Origin = handshake.Origin,
- SubProtocol = handshake.SubProtocol,
- AnswerBytes = GenerateAnswerBytes(handshake.Key1, handshake.Key2, handshake.ChallengeBytes)
- };
-
- return responseHandshake;
- }
-
- private static void SendServerHandshake(ServerHandshake handshake, Context context)
- {
- // generate a byte array representation of the handshake including the answer to the challenge
- byte[] handshakeBytes = Encoding.UTF8.GetBytes(handshake.ToString());
- Array.Copy(handshake.AnswerBytes, 0, handshakeBytes, handshakeBytes.Length - 16, 16);
-
- context.UserContext.Send(handshakeBytes, true);
- }
-
- private static byte[] TranslateKey(string key)
- {
- // Count total spaces in the keys
- int keySpaceCount = key.Count(x => x == ' ');
-
- // Get a number which is a concatenation of all digits in the keys.
- var keyNumberString = new String(key.Where(Char.IsDigit).ToArray());
-
- // Divide the number with the number of spaces
- var keyResult = (Int32) (Int64.Parse(keyNumberString)/keySpaceCount);
-
- // convert the results to 32 bit big endian byte arrays
- byte[] keyResultBytes = BitConverter.GetBytes(keyResult);
- if (BitConverter.IsLittleEndian)
- {
- Array.Reverse(keyResultBytes);
- }
- return keyResultBytes;
- }
-
- private static byte[] GenerateAnswerBytes(string key1, string key2, ArraySegment<byte> challenge)
- {
- // Translate the two keys, concatenate them and the 8 challenge bytes from the client
- var rawAnswer = new byte[16];
- Array.Copy(TranslateKey(key1), 0, rawAnswer, 0, 4);
- Array.Copy(TranslateKey(key2), 0, rawAnswer, 4, 4);
- Array.Copy(challenge.Array, challenge.Offset, rawAnswer, 8, 8);
-
- // Create a hash of the rawAnswer and return it
- MD5 hasher = MD5.Create();
- return hasher.ComputeHash(rawAnswer);
- }
- }
+using System;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using Alchemy.Classes;
+
+namespace Alchemy.Handlers.WebSocket.hybi00
+{
+ /// <summary>
+ /// Handles the handshaking between the client and the host, when a new connection is created
+ /// </summary>
+ internal class Authentication : Handlers.Authentication
+ {
+ protected override bool CheckAuthentication(Context context)
+ {
+ if (context.ReceivedByteCount > 8)
+ {
+ var handshake =
+ new ClientHandshake(new ArraySegment<byte>(context.Buffer, context.ReceivedByteCount - 8, 8),
+ context.Header);
+ // See if our header had the required information
+ if (handshake.IsValid())
+ {
+ // Optionally check Origin and Location if they're set.
+ if (Origin != string.Empty)
+ {
+ if (handshake.Origin != "http://" + Origin)
+ {
+ return false;
+ }
+ }
+ if (Destination != string.Empty)
+ {
+ if (handshake.Host != Destination + ":" + context.Server.Port)
+ {
+ return false;
+ }
+ }
+ // Generate response handshake for the client
+ var serverShake = GenerateResponseHandshake(handshake, context.Server);
+ // Send the response handshake
+ SendServerHandshake(serverShake, context);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static ServerHandshake GenerateResponseHandshake(ClientHandshake handshake, WebSocketServer server)
+ {
+ var responseHandshake = new ServerHandshake()
+ {
+ Location = "ws://" + handshake.Host + handshake.ResourcePath,
+ Origin = handshake.Origin,
+ AnswerBytes = GenerateAnswerBytes(handshake.Key1, handshake.Key2, handshake.ChallengeBytes),
+ Server = server,
+ RequestProtocols = server.SubProtocols
+ };
+
+ return responseHandshake;
+ }
+
+ private static void SendServerHandshake(ServerHandshake handshake, Context context)
+ {
+ // generate a byte array representation of the handshake including the answer to the challenge
+ byte[] handshakeBytes = Encoding.UTF8.GetBytes(handshake.ToString());
+ Array.Copy(handshake.AnswerBytes, 0, handshakeBytes, handshakeBytes.Length - 16, 16);
+
+ context.UserContext.Send(handshakeBytes, true);
+ }
+
+ private static byte[] TranslateKey(string key)
+ {
+ // Count total spaces in the keys
+ var keySpaceCount = key.Count(x => x == ' ');
+
+ // Get a number which is a concatenation of all digits in the keys.
+ var keyNumberString = new String(key.Where(Char.IsDigit).ToArray());
+
+ // Divide the number with the number of spaces
+ var keyResult = (Int32) (Int64.Parse(keyNumberString)/keySpaceCount);
+
+ // convert the results to 32 bit big endian byte arrays
+ byte[] keyResultBytes = BitConverter.GetBytes(keyResult);
+ if (BitConverter.IsLittleEndian)
+ {
+ Array.Reverse(keyResultBytes);
+ }
+ return keyResultBytes;
+ }
+
+ private static byte[] GenerateAnswerBytes(string key1, string key2, ArraySegment<byte> challenge)
+ {
+ // Translate the two keys, concatenate them and the 8 challenge bytes from the client
+ var rawAnswer = new byte[16];
+ Array.Copy(TranslateKey(key1), 0, rawAnswer, 0, 4);
+ Array.Copy(TranslateKey(key2), 0, rawAnswer, 4, 4);
+ Array.Copy(challenge.Array, challenge.Offset, rawAnswer, 8, 8);
+
+ // Create a hash of the rawAnswer and return it
+ var hasher = MD5.Create();
+ return hasher.ComputeHash(rawAnswer);
+ }
+ }
}
View
306 src/Alchemy/Handlers/WebSocket/hybi00/Handshakes.cs
@@ -1,148 +1,160 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Web;
-using Alchemy.Classes;
-
-namespace Alchemy.Handlers.WebSocket.hybi00
-{
- /// <summary>
- /// An easy wrapper for the header to access client handshake data.
- /// See http://www.whatwg.org/specs/web-socket-protocol/ for more details on the WebSocket Protocol.
- /// </summary>
- public class ClientHandshake
- {
- /// <summary>
- /// The preformatted handshake as a string.
- /// </summary>
- private const String Handshake =
- "GET {0} HTTP/1.1\r\n" +
- "Upgrade: WebSocket\r\n" +
- "Connection: Upgrade\r\n" +
- "Origin: {1}\r\n" +
- "Host: {2}\r\n" +
- "Sec-Websocket-Key1: {3}\r\n" +
- "Sec-Websocket-Key2: {4}\r\n" +
- "{5}";
-
- public string Host = String.Empty;
- public string Key1 = String.Empty;
- public string Key2 = String.Empty;
- public string Origin = String.Empty;
- public string ResourcePath = String.Empty;
-
- public ClientHandshake() {}
-
- /// <summary>
- /// Initializes a new instance of the <see cref="ClientHandshake"/> class.
- /// </summary>
- /// <param name="challengeBytes">The challenge bytes.</param>
- /// <param name="header">The header.</param>
- public ClientHandshake(ArraySegment<byte> challengeBytes, Header header)
- {
- ChallengeBytes = challengeBytes;
- ResourcePath = header.RequestPath;
- Key1 = header["sec-websocket-key1"];
- Key2 = header["sec-websocket-key2"];
- SubProtocol = header["sec-websocket-protocol"];
- Origin = header["origin"];
- Host = header["host"];
- Cookies = header.Cookies;
- }
-
- public ArraySegment<byte> ChallengeBytes { get; set; }
- public HttpCookieCollection Cookies { get; set; }
- public string SubProtocol { get; set; }
- public Dictionary<string, string> AdditionalFields { get; set; }
-
- /// <summary>
- /// Determines whether this instance is valid.
- /// </summary>
- /// <returns>
- /// <c>true</c> if this instance is valid; otherwise, <c>false</c>.
- /// </returns>
- public bool IsValid()
- {
- return (
- (Host != null) &&
- (Key1 != null) &&
- (Key2 != null) &&
- (Origin != null) &&
- (ResourcePath != null)
- );
- }
-
- /// <summary>
- /// Returns a <see cref="System.String"/> that represents this instance.
- /// </summary>
- /// <returns>
- /// A <see cref="System.String"/> that represents this instance.
- /// </returns>
- public override string ToString()
- {
- string additionalFields = String.Empty;
-
- if (Cookies != null)
- {
- additionalFields += "Cookie: " + Cookies + "\r\n";
- }
- if (SubProtocol != null)
- {
- additionalFields += "Sec-Websocket-Protocol: " + SubProtocol + "\r\n";
- }
-
- if (additionalFields != String.Empty)
- {
- additionalFields = AdditionalFields.Aggregate(additionalFields,
- (current, field) =>
- current + (field.Key + ": " + field.Value + "\r\n"));
- }
- additionalFields += "\r\n";
-
- return String.Format(Handshake, ResourcePath, Origin, Host, Key1, Key2, additionalFields);
- }
- }
-
- /// <summary>
- /// Implements a server handshake
- /// See http://www.whatwg.org/specs/web-socket-protocol/ for more details on the WebSocket Protocol.
- /// </summary>
- public class ServerHandshake
- {
- /// <summary>
- /// The preformatted handshake string.
- /// </summary>
- private const string Handshake =
- "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" +
- "Upgrade: WebSocket\r\n" +
- "Connection: Upgrade\r\n" +
- "Sec-WebSocket-Origin: {0}\r\n" +
- "Sec-WebSocket-Location: {1}\r\n" +
- "{2}" +
- " "; //Empty space for challenge answer
-
- public string Location = String.Empty;
- public string Origin = String.Empty;
- public byte[] AnswerBytes { get; set; }
- public string SubProtocol { get; set; }
- public Dictionary<string, string> AdditionalFields { get; set; }
-
- /// <summary>
- /// Returns a <see cref="System.String"/> that represents this instance.
- /// </summary>
- /// <returns>
- /// A <see cref="System.String"/> that represents this instance.
- /// </returns>
- public override string ToString()
- {
- string additionalFields = String.Empty;
- if (SubProtocol != null)
- {
- additionalFields += "Sec-WebSocket-Protocol: " + SubProtocol + "\r\n";
- }
- additionalFields += "\r\n";
-
- return String.Format(Handshake, Origin, Location, additionalFields);
- }
- }
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+using Alchemy.Classes;
+
+namespace Alchemy.Handlers.WebSocket.hybi00
+{
+ /// <summary>
+ /// An easy wrapper for the header to access client handshake data.
+ /// See http://www.whatwg.org/specs/web-socket-protocol/ for more details on the WebSocket Protocol.
+ /// </summary>
+ public class ClientHandshake
+ {
+ /// <summary>
+ /// The preformatted handshake as a string.
+ /// </summary>
+ private const String Handshake =
+ "GET {0} HTTP/1.1\r\n" +
+ "Upgrade: WebSocket\r\n" +
+ "Connection: Upgrade\r\n" +
+ "Origin: {1}\r\n" +
+ "Host: {2}\r\n" +
+ "Sec-Websocket-Key1: {3}\r\n" +
+ "Sec-Websocket-Key2: {4}\r\n" +
+ "{5}";
+
+ public string Host = String.Empty;
+ public string Key1 = String.Empty;
+ public string Key2 = String.Empty;
+ public string Origin = String.Empty;
+ public string ResourcePath = String.Empty;
+
+ public ClientHandshake() {}
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ClientHandshake"/> class.
+ /// </summary>
+ /// <param name="challengeBytes">The challenge bytes.</param>
+ /// <param name="header">The header.</param>
+ public ClientHandshake(ArraySegment<byte> challengeBytes, Header header)
+ {
+ ChallengeBytes = challengeBytes;
+ ResourcePath = header.RequestPath;
+ Key1 = header["sec-websocket-key1"];
+ Key2 = header["sec-websocket-key2"];
+ SubProtocols = header.SubProtocols;
+ Origin = header["origin"];
+ Host = header["host"];
+ Cookies = header.Cookies;
+ }
+
+ public ArraySegment<byte> ChallengeBytes { get; set; }
+ public HttpCookieCollection Cookies { get; set; }
+ public string[] SubProtocols { get; set; }
+ public Dictionary<string, string> AdditionalFields { get; set; }
+
+ /// <summary>
+ /// Determines whether this instance is valid.
+ /// </summary>
+ /// <returns>
+ /// <c>true</c> if this instance is valid; otherwise, <c>false</c>.
+ /// </returns>
+ public bool IsValid()
+ {
+ return (
+ (Host != null) &&
+ (Key1 != null) &&
+ (Key2 != null) &&
+ (Origin != null) &&
+ (ResourcePath != null)
+ );
+ }
+
+ /// <summary>
+ /// Returns a <see cref="System.String"/> that represents this instance.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="System.String"/> that represents this instance.
+ /// </returns>
+ public override string ToString()
+ {
+ var additionalFields = String.Empty;
+
+ if (Cookies != null)
+ {
+ additionalFields += "Cookie: " + Cookies + "\r\n";
+ }
+
+ if (SubProtocols != null && SubProtocols.Length > 0)
+ {
+ additionalFields += "Sec-Websocket-Protocol: " + String.Join(",", SubProtocols) + "\r\n";
+ }
+
+ if (additionalFields != String.Empty)
+ {
+ additionalFields = AdditionalFields.Aggregate(additionalFields,
+ (current, field) =>
+ current + (field.Key + ": " + field.Value + "\r\n"));
+ }
+ additionalFields += "\r\n";
+
+ return String.Format(Handshake, ResourcePath, Origin, Host, Key1, Key2, additionalFields);
+ }
+ }
+
+ /// <summary>
+ /// Implements a server handshake
+ /// See http://www.whatwg.org/specs/web-socket-protocol/ for more details on the WebSocket Protocol.
+ /// </summary>
+ public class ServerHandshake
+ {
+ /// <summary>
+ /// The preformatted handshake string.
+ /// </summary>
+ private const string Handshake =
+ "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" +
+ "Upgrade: WebSocket\r\n" +
+ "Connection: Upgrade\r\n" +
+ "Sec-WebSocket-Origin: {0}\r\n" +
+ "Sec-WebSocket-Location: {1}\r\n" +
+ "{2}" +
+ " "; //Empty space for challenge answer
+
+ public string Location = String.Empty;
+ public string Origin = String.Empty;
+ public byte[] AnswerBytes { get; set; }
+ public string[] RequestProtocols { get; set; }
+
+ public WebSocketServer Server { get; set; }
+
+ /// <summary>
+ /// Returns a <see cref="System.String"/> that represents this instance.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="System.String"/> that represents this instance.
+ /// </returns>
+ public override string ToString()
+ {
+ var additionalFields = String.Empty;
+
+ if (Server.SubProtocols != null && RequestProtocols != null)
+ {
+ foreach (var s in RequestProtocols)
+ {
+ if (Server.SubProtocols.Contains(s))
+ {
+ additionalFields += "Sec-WebSocket-Protocol: " + s + "\r\n";
+ }
+
+ break;
+ }
+ }
+
+ additionalFields += "\r\n";
+
+ return String.Format(Handshake, Origin, Location, additionalFields);
+ }
+ }
}
View
145 ...chemy/Handlers/WebSocket/hybi10/Authentication.cs → ...hemy/Handlers/WebSocket/rfc6455/Authentication.cs
@@ -1,74 +1,73 @@
-using System;
-using System.Security.Cryptography;
-using System.Text;
-using Alchemy.Classes;
-
-namespace Alchemy.Handlers.WebSocket.hybi10
-{
- /// <summary>
- /// Handles the handshaking between the client and the host, when a new connection is created
- /// </summary>
- internal class Authentication : Handlers.Authentication
- {
- protected override bool CheckAuthentication(Context context)
- {
- if (context.ReceivedByteCount > 8)
- {
- var handshake = new ClientHandshake(context.Header);
- // See if our header had the required information
- if (handshake.IsValid())
- {
- // Optionally check Origin and Location if they're set.
- if (!String.IsNullOrEmpty(Origin))
- {
- if (handshake.Origin != "http://" + Origin)
- {
- return false;
- }
- }
- if (!String.IsNullOrEmpty(Destination))
- {
- if (handshake.Host != Destination + ":" + context.Server.Port.ToString())
- {
- return false;
- }
- }
- // Generate response handshake for the client
- ServerHandshake serverShake = GenerateResponseHandshake(handshake);
- serverShake.SubProtocol = handshake.SubProtocol;
- // Send the response handshake
- SendServerHandshake(serverShake, context);
- return true;
- }
- }
- return false;
- }
-
- private static ServerHandshake GenerateResponseHandshake(ClientHandshake handshake)
- {
- var responseHandshake = new ServerHandshake {Accept = GenerateAccept(handshake.Key)};
- return responseHandshake;
- }
-
- private static void SendServerHandshake(ServerHandshake handshake, Context context)
- {
- // generate a byte array representation of the handshake including the answer to the challenge
- string temp = handshake.ToString();
- byte[] handshakeBytes = Encoding.UTF8.GetBytes(temp);
- context.UserContext.Send(handshakeBytes, true);
- }
-
- public static string GenerateAccept(string key)
- {
- if (!String.IsNullOrEmpty(key))
- {
- string rawAnswer = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
-
- // Create a hash of the rawAnswer and return it
- SHA1 hasher = SHA1.Create();
- return Convert.ToBase64String(hasher.ComputeHash(Encoding.UTF8.GetBytes(rawAnswer)));
- }
- return String.Empty;
- }
- }
+using System;
+using System.Security.Cryptography;
+using System.Text;
+using Alchemy.Classes;
+
+namespace Alchemy.Handlers.WebSocket.rfc6455
+{
+ /// <summary>
+ /// Handles the handshaking between the client and the host, when a new connection is created
+ /// </summary>
+ internal class Authentication : Handlers.Authentication
+ {
+ protected override bool CheckAuthentication(Context context)
+ {
+ if (context.ReceivedByteCount > 8)
+ {
+ var handshake = new ClientHandshake(context.Header);
+ // See if our header had the required information
+ if (handshake.IsValid())
+ {
+ // Optionally check Origin and Location if they're set.
+ if (!String.IsNullOrEmpty(Origin))
+ {
+ if (handshake.Origin != "http://" + Origin)
+ {
+ return false;
+ }
+ }
+ if (!String.IsNullOrEmpty(Destination))
+ {
+ if (handshake.Host != Destination + ":" + context.Server.Port)
+ {
+ return false;
+ }
+ }
+ // Generate response handshake for the client
+ var serverShake = GenerateResponseHandshake(handshake, context.Server);
+ // Send the response handshake
+ SendServerHandshake(serverShake, context);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static ServerHandshake GenerateResponseHandshake(ClientHandshake handshake, WebSocketServer server)
+ {
+ var responseHandshake = new ServerHandshake {Accept = GenerateAccept(handshake.Key), Server = server, RequestProtocols = handshake.SubProtocols};
+ return responseHandshake;
+ }
+
+ private static void SendServerHandshake(ServerHandshake handshake, Context context)
+ {
+ // generate a byte array representation of the handshake including the answer to the challenge
+ string temp = handshake.ToString();
+ byte[] handshakeBytes = Encoding.UTF8.GetBytes(temp);
+ context.UserContext.Send(handshakeBytes, true);
+ }
+
+ public static string GenerateAccept(string key)
+ {
+ if (!String.IsNullOrEmpty(key))
+ {
+ var rawAnswer = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+
+ // Create a hash of the rawAnswer and return it
+ var hasher = SHA1.Create();
+ return Convert.ToBase64String(hasher.ComputeHash(Encoding.UTF8.GetBytes(rawAnswer)));
+ }
+ return String.Empty;
+ }
+ }
}
View
8 src/Alchemy/Handlers/WebSocket/hybi10/DataFrame.cs → src/Alchemy/Handlers/WebSocket/rfc6455/DataFrame.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
-namespace Alchemy.Handlers.WebSocket.hybi10
+namespace Alchemy.Handlers.WebSocket.rfc6455
{
/// <summary>
/// Simple WebSocket data Frame implementation.
@@ -110,7 +110,7 @@ public override void Append(byte[] someBytes, bool asFrame = false)
int dataStart = headerBytes.Length;
data =
new byte[
- Math.Min(Convert.ToInt32(Math.Min(_header.PayloadSizeRemaining, int.MaxValue)),
+ Math.Min(Convert.ToInt32(Math.Min(_header.PayloadSizeRemaining, int.MaxValue)),
someBytes.Length - dataStart)];
dataLength = Math.Min(_header.PayloadSizeRemaining, Convert.ToUInt64(someBytes.Length - dataStart));
Array.Copy(someBytes, dataStart, data, 0, Convert.ToInt32(dataLength));
@@ -118,8 +118,8 @@ public override void Append(byte[] someBytes, bool asFrame = false)
}
else
{
- dataLength = Math.Min(Convert.ToUInt64(data.Length), _header.PayloadSizeRemaining);
- data = new byte[dataLength];
+ dataLength = Math.Min(Convert.ToUInt64(data.Length), _header.PayloadSizeRemaining);
+ data = new byte[dataLength];
Array.Copy(someBytes, 0, data, 0, Convert.ToInt32(dataLength));
}
View
30 src/Alchemy/Handlers/WebSocket/hybi10/FrameHeader.cs → ...Alchemy/Handlers/WebSocket/rfc6455/FrameHeader.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Linq;
-namespace Alchemy.Handlers.WebSocket.hybi10
+namespace Alchemy.Handlers.WebSocket.rfc6455
{
internal class FrameHeader
{
@@ -30,22 +30,26 @@ public byte[] FromBytes(byte[] data)
//Combine bytes to form one large number
PayloadSize = (byte) (data[1] & 0x7F);
- if (PayloadSize == 126)
- {
- Array.Reverse(data, dataBegin, 2);
- PayloadSize = BitConverter.ToUInt16(data, dataBegin);
- dataBegin += 2;
- }
- else if (PayloadSize == 127)
- {
- Array.Reverse(data, dataBegin, 8);
- PayloadSize = BitConverter.ToUInt64(data, dataBegin);
- dataBegin += 8;
+
+ switch (PayloadSize)
+ {
+ case 126:
+ Array.Reverse(data, dataBegin, 2);
+ PayloadSize = BitConverter.ToUInt16(data, dataBegin);
+ dataBegin += 2;
+ break;
+ case 127:
+ Array.Reverse(data, dataBegin, 8);
+ PayloadSize = BitConverter.ToUInt64(data, dataBegin);
+ dataBegin += 8;
+ break;
}
+
PayloadSizeRemaining = PayloadSize;
IsMasked = Convert.ToBoolean((data[1] & 0x80) >> 7);
Mask = 0;
CurrentMaskIndex = 0;
+
if (IsMasked)
{
Mask = BitConverter.ToInt32(data, dataBegin);
@@ -64,7 +68,7 @@ public byte[] ToBytes(bool IsByte = false)
var headerBytes = new List<Byte[]>();
var data = new byte[1];
- if(IsByte)
+ if(IsByte)
data[0] = 0x82;
else
data[0] = 0x81;
View
58 src/Alchemy/Handlers/WebSocket/hybi10/Handler.cs → src/Alchemy/Handlers/WebSocket/rfc6455/Handler.cs
@@ -1,30 +1,30 @@
-namespace Alchemy.Handlers.WebSocket.hybi10
-{
- /// <summary>
- /// A threadsafe singleton that contains functions which are used to handle incoming connections for the WebSocket Protocol
- /// </summary>
- internal sealed class Handler : WebSocketHandler
- {
- private static Handler _instance;
-
- private Handler()
- {
- Authentication = new Authentication();
- }
-
- public new static Handler Instance
- {
- get
- {
- if (_instance != null)
- {
- return _instance;
- }
- CreateLock.Wait();
- _instance = new Handler();
- CreateLock.Release();
- return _instance;
- }
- }
- }
+namespace Alchemy.Handlers.WebSocket.rfc6455
+{
+ /// <summary>
+ /// A threadsafe singleton that contains functions which are used to handle incoming connections for the WebSocket Protocol
+ /// </summary>
+ internal sealed class Handler : WebSocketHandler
+ {
+ private static Handler _instance;
+
+ private Handler()
+ {
+ Authentication = new Authentication();
+ }
+
+ public new static Handler Instance
+ {
+ get
+ {
+ if (_instance != null)
+ {
+ return _instance;
+ }
+ CreateLock.Wait();
+ _instance = new Handler();
+ CreateLock.Release();
+ return _instance;
+ }
+ }
+ }
}
View
320 src/Alchemy/Handlers/WebSocket/hybi10/Handshakes.cs → src/Alchemy/Handlers/WebSocket/rfc6455/Handshakes.cs
@@ -1,152 +1,170 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Web;
-using Alchemy.Classes;
-
-namespace Alchemy.Handlers.WebSocket.hybi10
-{
- /// <summary>
- /// An easy wrapper for the header to access client handshake data.
- /// See http://www.whatwg.org/specs/web-socket-protocol/ for more details on the WebSocket Protocol.
- /// </summary>
- public class ClientHandshake
- {
- /// <summary>
- /// The preformatted handshake as a string.
- /// </summary>
- private const String Handshake =
- "GET {0} HTTP/1.1\r\n" +
- "Host: {2}\r\n" +
- "Origin: {1}\r\n" +
- "Upgrade: websocket\r\n" +
- "Connection: Upgrade\r\n" +
- "Sec-WebSocket-Key: {4}\r\n" +
- "Sec-WebSocket-Protocol: {3}\r\n" +
- "Sec-WebSocket-Version: 8\r\n" +
- "{5}";
-
- public string Host = String.Empty;
- public string Key = String.Empty;
- public string Origin = String.Empty;
- public string ResourcePath = String.Empty;
-
- public ClientHandshake() {}
-
- /// <summary>
- /// Initializes a new instance of the <see cref="ClientHandshake"/> class.
- /// </summary>
- /// <param name="header">The header.</param>
- public ClientHandshake(Header header)
- {
- ResourcePath = header.RequestPath;
- Key = header["sec-websocket-key"];
- SubProtocol = header["sec-websocket-protocol"];
- Origin = header["origin"];
- if (String.IsNullOrEmpty(Origin))
- {
- Origin = header["sec-websocket-origin"];
- }
- Host = header["host"];
- Version = header["sec-websocket-version"];
- Cookies = header.Cookies;
- }
-
- public HttpCookieCollection Cookies { get; set; }
- public string SubProtocol { get; set; }
- public string Version { get; set; }
- public Dictionary<string, string> AdditionalFields { get; set; }
-
- /// <summary>
- /// Determines whether this instance is valid.
- /// </summary>
- /// <returns>
- /// <c>true</c> if this instance is valid; otherwise, <c>false</c>.
- /// </returns>
- public bool IsValid()
- {
- return (
- (Host != null) &&
- (Key != null) &&
- (Int32.Parse(Version) >= 8)
- );
- }
-
- /// <summary>
- /// Returns a <see cref="System.String"/> that represents this instance.
- /// </summary>
- /// <returns>
- /// A <see cref="System.String"/> that represents this instance.
- /// </returns>
- public override string ToString()
- {
- string additionalFields = String.Empty;
-
- if (Cookies != null)
- {
- additionalFields += "Cookie: " + Cookies + "\r\n";
- }
-
- if (additionalFields != String.Empty)
- {
- additionalFields = AdditionalFields.Aggregate(additionalFields,
- (current, field) =>
- current + (field.Key + ": " + field.Value + "\r\n"));
- }
- additionalFields += "\r\n";
-
- return String.Format(Handshake, ResourcePath, Origin, Host, SubProtocol, Key, additionalFields);
- }
- }
-
- /// <summary>
- /// Implements a server handshake
- /// See http://www.whatwg.org/specs/web-socket-protocol/ for more details on the WebSocket Protocol.
- /// </summary>
- public class ServerHandshake
- {
- /// <summary>
- /// The preformatted handshake string.
- /// </summary>
- private const string Handshake =
- "HTTP/1.1 101 Switching Protocols\r\n" +
- "Upgrade: websocket\r\n" +
- "Connection: Upgrade\r\n" +
- "Sec-WebSocket-Accept: {0}\r\n" +
- "{1}";
-
- public ServerHandshake() {}
-
- /// <summary>
- /// Initializes a new instance of the <see cref="ServerHandshake"/> class.
- /// </summary>
- /// <param name="header">The header.</param>
- public ServerHandshake(Header header)
- {
- Accept = header["Sec-WebSocket-Accept"];
- SubProtocol = header["Sec-WebSocket-Protocol"];
- }
-
- public string Accept { get; set; }
- public string SubProtocol { get; set; }
-
- public Dictionary<string, string> AdditionalFields { get; set; }
-
- /// <summary>
- /// Returns a <see cref="System.String"/> that represents this instance.
- /// </summary>
- /// <returns>
- /// A <see cref="System.String"/> that represents this instance.
- /// </returns>
- public override string ToString()
- {
- string additionalFields = String.Empty;
- if (SubProtocol != null)
- {
- additionalFields += "Sec-WebSocket-Protocol: " + SubProtocol + "\r\n";
- }
- additionalFields += "\r\n";
- return String.Format(Handshake, Accept, additionalFields);
- }
- }
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+using Alchemy.Classes;
+
+namespace Alchemy.Handlers.WebSocket.rfc6455
+{
+ /// <summary>
+ /// An easy wrapper for the header to access client handshake data.
+ /// See http://www.whatwg.org/specs/web-socket-protocol/ for more details on the WebSocket Protocol.
+ /// </summary>
+ public class ClientHandshake
+ {
+ /// <summary>
+ /// The preformatted handshake as a string.
+ /// </summary>
+ private const String Handshake =
+ "GET {0} HTTP/1.1\r\n" +
+ "Host: {2}\r\n" +
+ "Origin: {1}\r\n" +
+ "Upgrade: websocket\r\n" +
+ "Connection: Upgrade\r\n" +
+ "Sec-WebSocket-Key: {3}\r\n" +
+ "Sec-WebSocket-Version: 8\r\n" +
+ "{4}";
+
+ public string Host = String.Empty;
+ public string Key = String.Empty;
+ public string Origin = String.Empty;
+ public string ResourcePath = String.Empty;
+
+ public ClientHandshake() {}
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ClientHandshake"/> class.
+ /// </summary>
+ /// <param name="header">The header.</param>
+ public ClientHandshake(Header header)
+ {
+ ResourcePath = header.RequestPath;
+ Key = header["sec-websocket-key"];
+ SubProtocols = header.SubProtocols;
+ Origin = header["origin"];
+ if (String.IsNullOrEmpty(Origin))
+ {
+ Origin = header["sec-websocket-origin"];
+ }
+ Host = header["host"];
+ Version = header["sec-websocket-version"];
+ Cookies = header.Cookies;
+ }
+
+ public HttpCookieCollection Cookies { get; set; }
+ public string[] SubProtocols { get; set; }
+ public string Version { get; set; }
+ public Dictionary<string, string> AdditionalFields { get; set; }
+
+ /// <summary>
+ /// Determines whether this instance is valid.
+ /// </summary>
+ /// <returns>
+ /// <c>true</c> if this instance is valid; otherwise, <c>false</c>.
+ /// </returns>
+ public bool IsValid()
+ {
+ return (
+ (Host != null) &&
+ (Key != null) &&
+ (Int32.Parse(Version) >= 8)
+ );
+ }
+
+ /// <summary>
+ /// Returns a <see cref="System.String"/> that represents this instance.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="System.String"/> that represents this instance.
+ /// </returns>
+ public override string ToString()
+ {
+ var additionalFields = String.Empty;
+
+ if (Cookies != null)
+ {
+ additionalFields += "Cookie: " + Cookies + "\r\n";
+ }
+
+ if (additionalFields != String.Empty)
+ {
+ additionalFields = AdditionalFields.Aggregate(additionalFields,
+ (current, field) =>
+ current + (field.Key + ": " + field.Value + "\r\n"));
+ }
+
+ if (SubProtocols != null && SubProtocols.Length > 0)
+ {
+ additionalFields += "Sec-Websocket-Protocol: " + String.Join(",", SubProtocols) + "\r\n";
+ }
+
+ additionalFields += "\r\n";
+
+ return String.Format(Handshake, ResourcePath, Origin, Host, Key, additionalFields);
+ }
+ }
+
+ /// <summary>
+ /// Implements a server handshake
+ /// See http://www.whatwg.org/specs/web-socket-protocol/ for more details on the WebSocket Protocol.
+ /// </summary>
+ public class ServerHandshake
+ {
+ /// <summary>
+ /// The preformatted handshake string.
+ /// </summary>
+ private const string Handshake =
+ "HTTP/1.1 101 Switching Protocols\r\n" +
+ "Upgrade: websocket\r\n" +
+ "Connection: Upgrade\r\n" +
+ "Sec-WebSocket-Accept: {0}\r\n" +
+ "{1}";
+
+ public ServerHandshake() {}
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ServerHandshake"/> class.
+ /// </summary>
+ /// <param name="header">The header.</param>
+ public ServerHandshake(Header header)
+ {
+ Accept = header["Sec-WebSocket-Accept"];
+ }
+
+ public string[] RequestProtocols { get; set; }
+
+ public string Accept { get; set; }
+
+ public WebSocketServer Server { get; set; }
+
+ /// <summary>
+ /// Returns a <see cref="System.String"/> that represents this instance.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="System.String"/> that represents this instance.
+ /// </returns>
+ public override string ToString()
+ {
+ var additionalFields = String.Empty;
+
+ if (Server.SubProtocols != null && RequestProtocols != null)
+ {
+ string subprotocol = "";
+
+ foreach (var s in RequestProtocols)
+ {
+ if (!Server.SubProtocols.Contains(s) || !String.IsNullOrEmpty(subprotocol)) continue;
+ subprotocol = s;
+ }
+
+ if(!String.IsNullOrEmpty(subprotocol))
+ {
+ additionalFields += "Sec-WebSocket-Protocol: " + subprotocol + "\r\n";
+ }
+ }
+
+ additionalFields += "\r\n";
+ return String.Format(Handshake, Accept, additionalFields);
+ }
+ }
}
View
517 src/Alchemy/WebSocketClient.cs
@@ -1,245 +1,274 @@
-using System;
-using System.Net.Sockets;
-using System.Text;
-using System.Text.RegularExpressions;
-using System.Threading;
-using Alchemy.Classes;
-using Alchemy.Handlers.WebSocket.hybi10;
-
-namespace Alchemy
-{
- public class WebSocketClient
- {
- public TimeSpan ConnectTimeout = new TimeSpan(0, 0, 0, 10);
- public bool IsAuthenticated;
- public ReadyStates ReadyState = ReadyStates.CLOSED;
- public string Origin;
-
- public OnEventDelegate OnConnect = x => { };
- public OnEventDelegate OnConnected = x => { };
- public OnEventDelegate OnDisconnect = x => { };
- public OnEventDelegate OnReceive = x => { };
- public OnEventDelegate OnSend = x => { };
-
- private TcpClient _client;
- private bool _connecting;
- private Context _context;
- private ClientHandshake _handshake;
-
- private readonly string _path;
- private readonly int _port;
- private readonly string _host;
-
- public enum ReadyStates
- {
- CONNECTING,
- OPEN,
- CLOSING,
- CLOSED
- }