/// <summary> /// Attempt to parse the given text into an HTTPClientHandshake object /// </summary> /// <returns><c>true</c>, if parse was successful <c>false</c> otherwise.</returns> /// <param name="text">The complete HTTP header to be parsed</param> /// <param name="result">The parsed header as an HTTPClientHandshake object</param> /// <param name="error">Any error message from parsing... just for you!</param> public static bool TryParse(string text, out HTTPClientHandshake result, out string error) { //Set some initial dingles error = ""; result = new HTTPClientHandshake(); List<string> lines; List<string> lineArguments; List<string> subArguments; //First, replace the garbage. text = text.Replace("\r\n", "\n"); //Now actually get the lines, dawg. lines = text.Split("\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToList(); lines = lines.Select(x => x.Trim()).ToList(); //Man, don't be givin' us no flack if (lines.Count < 1) { error = "Handshake was empty!"; return false; } //First line MUST be the GET thing. Break by space. lineArguments = Regex.Split(lines[0], @"\s+").ToList(); //It's a bad request or something. if (lineArguments.Count != 3 || lineArguments[0].ToUpperInvariant() != "GET") { error = "HTTP Request was poorly formatted! (1st argument)"; return false; } //Retrieve the service subArguments = lineArguments[1].Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToList(); if (subArguments.Count < 1) { error = "HTTP Request was poorly formatted! (2nd argument)"; return false; } result.Service = subArguments[subArguments.Count - 1]; //Retrieve the HTTP version subArguments = lineArguments[2].Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToList(); if (subArguments.Count != 2 || subArguments[0].ToUpperInvariant() != "HTTP") { error = "HTTP Request was poorly formatted! (3rd argument)"; return false; } else if ((new Version(subArguments[1])).CompareTo(new Version(ExpectedHTTPVersion)) < 0) { error = "HTTP version too low! (expected " + ExpectedHTTPVersion + "+)"; return false; } result.HTTPVersion = subArguments[1].Trim(); Dictionary<string, bool> completes = ExpectedFields.ToDictionary(x => x, y => false); //OK, NOW we can start retrieving those fields dawg. Some are required. for (int i = 1; i < lines.Count; i++) { Match match = Regex.Match(lines[i], @"^([a-zA-Z\-]+)\s*:\s*(.+)$"); //Ignore bad lines if (!match.Success) continue; string key = match.Groups[1].Value.Trim(); string field = match.Groups[2].Value.Trim(); List<string> values = WebSocketHelper.Explode(field); //Check expected field values for correctness if (key == "Upgrade" && field.ToLowerInvariant() != "websocket") { error = "Bad Upgrade field!"; return false; } else if (key == "Connection" && !values.Contains("Upgrade")) { error = "Bad Connection field! (" + field + ")"; return false; } else if (key == "Sec-WebSocket-Version" && field != ExpectedWebSocketVersion) { error = "Bad Sec-WebSocket-Version! (expected " + ExpectedWebSocketVersion + ")"; return false; } else if (key == "Host") { result.Host = field; } else if (key == "Sec-WebSocket-Key") { result.Key = field; } else if (key == "Origin") { result.Origin = field; } else if (key == "Sec-WebSocket-Protocol") { result.Protocols = values; } else if (key == "Sec-WebSocket-Extensions") { result.Extensions = values; } //If this is an expected field, say that we saw it (we would've quit if it was bad) if (completes.ContainsKey(key)) completes[key] = true; } return true; }
/// <summary> /// Read and properly parse the message for the HTTP handshake portion of /// a WebSocket connection. /// </summary> /// <returns>The read handshake.</returns> public async Task<Tuple<DataStatus, HTTPClientHandshake, string>> ReadHandshakeAsync() { //You've already done the handshake, you idiot. if (HandShakeComplete) return Tuple.Create(DataStatus.Complete, parsedHandshake, ""); string handshake = ""; int handshakeEnd = 0; string error = ""; HTTPClientHandshake result = new HTTPClientHandshake(); //Keep repeating until we have the whole handshake. It's OK if the stream stops in the middle //of the operation, because we'll just return the proper data status. do { //Pull a chunk of data (as much as we can) from the stream and store it in our internal buffer. DataStatus readStatus = await GenericReadAsync(); //If there was an error (anything other than "completion"), return the error. if (readStatus != DataStatus.Complete) return Tuple.Create(readStatus, result, ""); //Now let's see if we read the whole header by searching for the header ending symbol. handshake = System.Text.Encoding.ASCII.GetString(messageBuffer, 0, messageBufferSize); handshakeEnd = handshake.IndexOf("\r\n\r\n"); } while(handshakeEnd < 0); //We read the whole header, now it's time to parse it. if (HTTPClientHandshake.TryParse(handshake, out result, out error)) { //Push the data in the buffer back. We may have read a bit of the new data. MessageBufferPop(handshakeEnd + 4); // messageBuffer.TruncateBeginning(handshakeEnd + 4, messageBufferSize); // messageBufferSize -= (handshakeEnd + 4); parsedHandshake = result; return Tuple.Create(DataStatus.Complete, result, error); } else { return Tuple.Create(DataStatus.DataFormatError, result, error); } }
public static HTTPServerHandshake GetResponseForClientHandshake(HTTPClientHandshake handshake) { HTTPServerHandshake response = new HTTPServerHandshake(); response.AcceptedProtocols = new List<string>(handshake.Protocols); response.AcceptedExtensions = new List<string>(handshake.Extensions); response.AcceptKey = GenerateAcceptKey(handshake.Key); response.HTTPVersion = handshake.HTTPVersion; response.Status = "101 Switching Protocols"; return response; }
/// <summary> /// Attempts to pull all the data and properly parse it for the HTTP handshake portion of /// a WebSocket connection. Performs minimal blocking. /// </summary> /// <returns>The read handshake.</returns> public DataStatus TryReadHandshake(out HTTPClientHandshake result, out string error) { result = new HTTPClientHandshake(); error = ""; //You've already done the handshake, you idiot. if (HandShakeComplete) { result = parsedHandshake; return DataStatus.Complete; } //Pull a chunk of data (as much as we can) from the stream and store it in our internal buffer. DataStatus readStatus = GenericRead(); //If there was an error (anything other than "completion"), return the error. if (readStatus != DataStatus.Complete) return readStatus; //Now let's see if we read the whole header by searching for the header ending symbol. string handshake = System.Text.Encoding.ASCII.GetString(messageBuffer, 0, messageBufferSize); int handshakeEnd = handshake.IndexOf("\r\n\r\n"); //We read the whole header, now it's time to parse it. if(handshakeEnd >= 0) { if(HTTPClientHandshake.TryParse(handshake, out result, out error)) { //Push the data in the buffer back. We may have read a bit of the new data. MessageBufferPop(handshakeEnd + 4); // messageBuffer.TruncateBeginning(handshakeEnd + 4, messageBufferSize); // messageBufferSize -= (handshakeEnd + 4); parsedHandshake = result; return DataStatus.Complete; } else { return DataStatus.DataFormatError; } } else { //If we didn't read the whole header, we're still basically waiting on data. return DataStatus.WaitingOnData; } }