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

  1. #if !NET20
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Security.Cryptography;
  6. using System.Text;
  7. namespace Apewer.WebSocket
  8. {
  9. internal static class Draft76Handler
  10. {
  11. private const byte End = 255;
  12. private const byte Start = 0;
  13. private const int MaxSize = 1024 * 1024 * 5;
  14. public static ComposableHandler Create(HttpRequest request, Action<string> onMessage)
  15. {
  16. return new ComposableHandler
  17. {
  18. TextFrame = Draft76Handler.FrameText,
  19. Handshake = sub => Draft76Handler.Handshake(request, sub),
  20. ReceiveData = data => ReceiveData(onMessage, data)
  21. };
  22. }
  23. public static void ReceiveData(Action<string> onMessage, List<byte> data)
  24. {
  25. while (data.Count > 0)
  26. {
  27. if (data[0] != Start)
  28. throw new WebSocketException(StatusCodes.InvalidFramePayloadData);
  29. var endIndex = data.IndexOf(End);
  30. if (endIndex < 0)
  31. return;
  32. if (endIndex > MaxSize)
  33. throw new WebSocketException(StatusCodes.MessageTooBig);
  34. var bytes = data.Skip(1).Take(endIndex - 1).ToArray();
  35. data.RemoveRange(0, endIndex + 1);
  36. var message = Encoding.UTF8.GetString(bytes);
  37. onMessage(message);
  38. }
  39. }
  40. public static byte[] FrameText(string data)
  41. {
  42. byte[] bytes = Encoding.UTF8.GetBytes(data);
  43. // wrap the array with the wrapper bytes
  44. var wrappedBytes = new byte[bytes.Length + 2];
  45. wrappedBytes[0] = Start;
  46. wrappedBytes[wrappedBytes.Length - 1] = End;
  47. Array.Copy(bytes, 0, wrappedBytes, 1, bytes.Length);
  48. return wrappedBytes;
  49. }
  50. public static byte[] Handshake(HttpRequest request, string subProtocol)
  51. {
  52. WebSocketLog.Debug("Building Draft76 Response");
  53. var builder = new StringBuilder();
  54. builder.Append("HTTP/1.1 101 WebSocket Protocol Handshake\r\n");
  55. builder.Append("Upgrade: WebSocket\r\n");
  56. builder.Append("Connection: Upgrade\r\n");
  57. builder.AppendFormat("Sec-WebSocket-Origin: {0}\r\n", request["Origin"]);
  58. builder.AppendFormat("Sec-WebSocket-Location: {0}://{1}{2}\r\n", request.Scheme, request["Host"], request.Path);
  59. if (subProtocol != null)
  60. builder.AppendFormat("Sec-WebSocket-Protocol: {0}\r\n", subProtocol);
  61. builder.Append("\r\n");
  62. var key1 = request["Sec-WebSocket-Key1"];
  63. var key2 = request["Sec-WebSocket-Key2"];
  64. var challenge = new ArraySegment<byte>(request.Bytes, request.Bytes.Length - 8, 8);
  65. var answerBytes = CalculateAnswerBytes(key1, key2, challenge);
  66. byte[] byteResponse = Encoding.ASCII.GetBytes(builder.ToString());
  67. int byteResponseLength = byteResponse.Length;
  68. Array.Resize(ref byteResponse, byteResponseLength + answerBytes.Length);
  69. Array.Copy(answerBytes, 0, byteResponse, byteResponseLength, answerBytes.Length);
  70. return byteResponse;
  71. }
  72. public static byte[] CalculateAnswerBytes(string key1, string key2, ArraySegment<byte> challenge)
  73. {
  74. byte[] result1Bytes = ParseKey(key1);
  75. byte[] result2Bytes = ParseKey(key2);
  76. var rawAnswer = new byte[16];
  77. Array.Copy(result1Bytes, 0, rawAnswer, 0, 4);
  78. Array.Copy(result2Bytes, 0, rawAnswer, 4, 4);
  79. Array.Copy(challenge.Array, challenge.Offset, rawAnswer, 8, 8);
  80. return MD5.Create().ComputeHash(rawAnswer);
  81. }
  82. private static byte[] ParseKey(string key)
  83. {
  84. int spaces = key.Count(x => x == ' ');
  85. var digits = new String(key.Where(Char.IsDigit).ToArray());
  86. var value = (Int32)(Int64.Parse(digits) / spaces);
  87. byte[] result = BitConverter.GetBytes(value);
  88. if (BitConverter.IsLittleEndian)
  89. Array.Reverse(result);
  90. return result;
  91. }
  92. }
  93. }
  94. #endif