public static byte[] GenerateResponseHandshake(ClientHandshake clientHandshake) { var responseHandshake = new ServerHandshake(); responseHandshake.Location = "ws://" + clientHandshake.Host + clientHandshake.ResourcePath; responseHandshake.Origin = clientHandshake.Origin; responseHandshake.SubProtocol = clientHandshake.SubProtocol; var challenge = new byte[8]; Array.Copy(clientHandshake.ChallengeBytes.Array, clientHandshake.ChallengeBytes.Offset, challenge, 0, 8); responseHandshake.AnswerBytes = CalculateAnswerBytes(clientHandshake.Key1, clientHandshake.Key2, clientHandshake.ChallengeBytes); return CreateServerHandshake(responseHandshake); }
public static ClientHandshake ParseClientHandshake(ArraySegment<byte> byteShake) { // the "grammar" of the handshake var pattern = @"^(?<connect>[^\s]+)\s(?<path>[^\s]+)\sHTTP\/1\.1\r\n" + // request line @"((?<field_name>[^:\r\n]+):\s(?<field_value>[^\r\n]+)\r\n)+"; // unordered set of fields (name-chars colon space any-chars cr lf) // subtract the challenge bytes from the handshake var handshake = new ClientHandshake(); ArraySegment<byte> challenge = new ArraySegment<byte>(byteShake.Array, byteShake.Count - 8, 8); // -8 : eight byte challenge handshake.ChallengeBytes = challenge; // get the rest of the handshake var utf8_handshake = Encoding.UTF8.GetString(byteShake.Array, 0, byteShake.Count - 8); // match the handshake against the "grammar" var regex = new Regex(pattern, RegexOptions.IgnoreCase); var match = regex.Match(utf8_handshake); var fields = match.Groups; // save the request path handshake.ResourcePath = fields["path"].Value; // run through every match and save them in the handshake object for (int i = 0; i < fields["field_name"].Captures.Count; i++) { var name = fields["field_name"].Captures[i].ToString(); var value = fields["field_value"].Captures[i].ToString(); switch (name.ToLower()) { case "sec-websocket-key1": handshake.Key1 = value; break; case "sec-websocket-key2": handshake.Key2 = value; break; case "sec-websocket-protocol": handshake.SubProtocol = value; break; case "origin": handshake.Origin = value; break; case "host": handshake.Host = value; break; case "cookie": // create and fill a cookie collection from the data in the handshake handshake.Cookies = new HttpCookieCollection(); var cookies = value.Split(';'); foreach (var item in cookies) { // the name if before the '=' char var c_name = item.Remove(item.IndexOf('=')); // the value is after var c_value = item.Substring(item.IndexOf('=') + 1); // put the cookie in the collection (this also parses the sub-values and such) handshake.Cookies.Add(new HttpCookie(c_name.TrimStart(), c_value)); } break; default: // some field that we don't know about if (handshake.AdditionalFields == null) handshake.AdditionalFields = new Dictionary<string, string>(); handshake.AdditionalFields[name] = value; break; } } return handshake; }
/// <summary> /// Establishes the connection /// </summary> public void Connect() { string host = uri.Host; StringBuilder path = new StringBuilder(uri.AbsolutePath); if (path.Length == 0) { path.Append('/'); } string query = uri.Query; if (!string.IsNullOrEmpty(query)) { path.Append("?"); path.Append(query); } string origin = "http://" + host; networkStream = CreateSocket(); if (networkStream != null) { if (uri.Port != 80) { host = host + ":" + uri.Port; } ClientHandshake shake = new ClientHandshake(); shake.Host = host; shake.Origin = origin; shake.AdditionalFields = headers; shake.Key1 = Guid.NewGuid().ToString(); shake.Key2 = Guid.NewGuid().ToString(); shake.Key1 = shake.Key1.Replace('-', ' ').Substring(0, 10); shake.Key2 = shake.Key2.Replace('-', ' ').Substring(0, 10); var baseChallenge = Guid.NewGuid().ToString().Substring(0, 8); var challenge = baseChallenge.Substring(0, 2) + " " + baseChallenge.Substring(3, 2) + " " + baseChallenge.Substring(5, 2); shake.ChallengeBytes = new ArraySegment<byte>(Encoding.UTF8.GetBytes(challenge)); shake.ResourcePath = path.ToString(); //outputStream = new StreamWriter(networkStream, Encoding.UTF8); var response = shake.ToString(); byte[] encodedHandshake = Encoding.UTF8.GetBytes(response); networkStream.Write(encodedHandshake, 0, encodedHandshake.Length); networkStream.Flush(); //This needs to be implemented for security // var expectedAnswer = Encoding.UTF8.GetString(HandshakeHelper.CalculateAnswerBytes(shake.Key1, shake.Key2, shake.ChallengeBytes)); inputStream = new StreamReader(networkStream); //var input = inputStream.ReadToEnd(); string header = inputStream.ReadLine(); if (!header.Equals("HTTP/1.1 101 WebSocket Protocol Handshake")) { if (OnError != null) OnError(new InvalidOperationException("Invalid handshake response")); throw new InvalidOperationException("Invalid handshake response"); } header = inputStream.ReadLine(); if (!header.Equals("Upgrade: WebSocket")) { if (OnError != null) OnError(new InvalidOperationException("Invalid handshake response")); throw new InvalidOperationException("Invalid handshake response"); } header = inputStream.ReadLine(); if (!header.Equals("Connection: Upgrade")) { if (OnError != null) OnError(new InvalidOperationException("Invalid handshake response")); throw new InvalidOperationException("Invalid handshake response"); } // Ignore any further response do { header = inputStream.ReadLine(); } while (!header.Equals("")); handshakeComplete = true; connection = new WebSocketConnection(networkStream, isSocketIo); SubscribeToConnectionEvents(); } else { throw new InvalidOperationException("Could not create socket"); } }