You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

120 lines
4.4 KiB

#if !NET20
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace Apewer.WebSocket
{
internal static class Draft76Handler
{
private const byte End = 255;
private const byte Start = 0;
private const int MaxSize = 1024 * 1024 * 5;
public static ComposableHandler Create(HttpRequest request, Action<string> onMessage)
{
return new ComposableHandler
{
TextFrame = Draft76Handler.FrameText,
Handshake = sub => Draft76Handler.Handshake(request, sub),
ReceiveData = data => ReceiveData(onMessage, data)
};
}
public static void ReceiveData(Action<string> onMessage, List<byte> data)
{
while (data.Count > 0)
{
if (data[0] != Start)
throw new WebSocketException(StatusCodes.InvalidFramePayloadData);
var endIndex = data.IndexOf(End);
if (endIndex < 0)
return;
if (endIndex > MaxSize)
throw new WebSocketException(StatusCodes.MessageTooBig);
var bytes = data.Skip(1).Take(endIndex - 1).ToArray();
data.RemoveRange(0, endIndex + 1);
var message = Encoding.UTF8.GetString(bytes);
onMessage(message);
}
}
public static byte[] FrameText(string data)
{
byte[] bytes = Encoding.UTF8.GetBytes(data);
// wrap the array with the wrapper bytes
var wrappedBytes = new byte[bytes.Length + 2];
wrappedBytes[0] = Start;
wrappedBytes[wrappedBytes.Length - 1] = End;
Array.Copy(bytes, 0, wrappedBytes, 1, bytes.Length);
return wrappedBytes;
}
public static byte[] Handshake(HttpRequest request, string subProtocol)
{
WebSocketLog.Debug("Building Draft76 Response");
var builder = new StringBuilder();
builder.Append("HTTP/1.1 101 WebSocket Protocol Handshake\r\n");
builder.Append("Upgrade: WebSocket\r\n");
builder.Append("Connection: Upgrade\r\n");
builder.AppendFormat("Sec-WebSocket-Origin: {0}\r\n", request["Origin"]);
builder.AppendFormat("Sec-WebSocket-Location: {0}://{1}{2}\r\n", request.Scheme, request["Host"], request.Path);
if (subProtocol != null)
builder.AppendFormat("Sec-WebSocket-Protocol: {0}\r\n", subProtocol);
builder.Append("\r\n");
var key1 = request["Sec-WebSocket-Key1"];
var key2 = request["Sec-WebSocket-Key2"];
var challenge = new ArraySegment<byte>(request.Bytes, request.Bytes.Length - 8, 8);
var answerBytes = CalculateAnswerBytes(key1, key2, challenge);
byte[] byteResponse = Encoding.ASCII.GetBytes(builder.ToString());
int byteResponseLength = byteResponse.Length;
Array.Resize(ref byteResponse, byteResponseLength + answerBytes.Length);
Array.Copy(answerBytes, 0, byteResponse, byteResponseLength, answerBytes.Length);
return byteResponse;
}
public static byte[] CalculateAnswerBytes(string key1, string key2, ArraySegment<byte> challenge)
{
byte[] result1Bytes = ParseKey(key1);
byte[] result2Bytes = ParseKey(key2);
var rawAnswer = new byte[16];
Array.Copy(result1Bytes, 0, rawAnswer, 0, 4);
Array.Copy(result2Bytes, 0, rawAnswer, 4, 4);
Array.Copy(challenge.Array, challenge.Offset, rawAnswer, 8, 8);
return MD5.Create().ComputeHash(rawAnswer);
}
private static byte[] ParseKey(string key)
{
int spaces = key.Count(x => x == ' ');
var digits = new String(key.Where(Char.IsDigit).ToArray());
var value = (Int32)(Int64.Parse(digits) / spaces);
byte[] result = BitConverter.GetBytes(value);
if (BitConverter.IsLittleEndian)
Array.Reverse(result);
return result;
}
}
}
#endif