protected override int ProcessHeadersAndUpgrade(NetContext context, Connection conn, Stream input, Stream additionalData, int additionalOffset) { string requestLine; var headers = ParseHeaders(input, out requestLine); string host = headers["Host"]; // context.Handler.WriteLog(host + ": " + requestLine, conn); //The "Request-URI" of the GET method [RFC2616] is used to identify the //endpoint of the WebSocket connection, both to allow multiple domains //to be served from one IP address and to allow multiple WebSocket //endpoints to be served by a single server. var connection = (WebSocketConnection)conn; connection.Host = host; connection.Origin = headers["Origin"] ?? headers["Sec-WebSocket-Origin"]; // Some early drafts used the latter, so we'll allow it as a fallback // in particular, two drafts of version "8" used (separately) **both**, // so we can't rely on the version for this (hybi-10 vs hybi-11). // To make it even worse, hybi-00 used Origin, so it is all over the place! connection.Protocol = headers["Sec-WebSocket-Protocol"]; connection.RequestLine = requestLine; if (string.IsNullOrEmpty(connection.Host)) { //4. The request MUST contain a |Host| header field whose value //contains /host/ plus optionally ":" followed by /port/ (when not //using the default port). throw new InvalidOperationException("host required"); } bool looksGoodEnough = false; // mozilla sends "keep-alive, Upgrade"; let's make it more forgiving var connectionParts = new HashSet <string>(StringComparer.InvariantCultureIgnoreCase); if (headers.ContainsKey("Connection")) { // so for mozilla, this will be the set {"keep-alive", "Upgrade"} var parts = headers["Connection"].Split(Comma); foreach (var part in parts) { connectionParts.Add(part.Trim()); } } if (connectionParts.Contains("Upgrade") && string.Equals(headers["Upgrade"], "websocket", StringComparison.InvariantCultureIgnoreCase)) { //5. The request MUST contain an |Upgrade| header field whose value //MUST include the "websocket" keyword. //6. The request MUST contain a |Connection| header field whose value //MUST include the "Upgrade" token. looksGoodEnough = true; } if (!looksGoodEnough && AllowClientsMissingConnectionHeaders) { if ((headers.ContainsKey("Sec-WebSocket-Version") && headers.ContainsKey("Sec-WebSocket-Key")) || (headers.ContainsKey("Sec-WebSocket-Key1") && headers.ContainsKey("Sec-WebSocket-Key2"))) { looksGoodEnough = true; } } if (looksGoodEnough) { //9. The request MUST include a header field with the name //|Sec-WebSocket-Version|. The value of this header field MUST be WebSocketsProcessor newProtocol; string version = headers["Sec-WebSocket-Version"]; byte[] body = null; int bodyRead = 0; bool processExtensions = false; if (version == null) { if (headers.ContainsKey("Sec-WebSocket-Key1") && headers.ContainsKey("Sec-WebSocket-Key2")) { // smells like hixie-76/hybi-00 newProtocol = new WebSocketsProcessor_Hixie76_00.Server(); } else { throw new NotSupportedException(); } } else { switch (version) { case "4": case "5": case "6": case "7": case "8": // these are all early drafts case "13": // this is later drafts and RFC6455 newProtocol = new WebSocketsProcessor_RFC6455_13(false); processExtensions = true; break; default: // should issues a 400 "upgrade required" and specify Sec-WebSocket-Version - see 4.4 throw new InvalidOperationException(string.Format("Sec-WebSocket-Version {0} is not supported", version)); } } //if(headers.ContainsKey("x-forwarded-for")) //{ // context.Handler.WriteLog("forwarded for: " + headers["x-forwarded-for"], conn); //} if (processExtensions) { AddMatchingExtensions(headers, connection, context.Extensions); } connection.HighPriority = false; // regular priority when we've got as far as switching protocols connection.SetProtocol(newProtocol); context.Handler.OnAuthenticate(connection, headers); newProtocol.CompleteHandshake(context, connection, input, requestLine, headers, body); context.Handler.OnAfterAuthenticate(connection); connection.PromptToSend(context); return(bodyRead); } else { var responseHeaders = new StringDictionary(); HttpStatusCode code; string body; if (!TryBasicResponse(context, headers, requestLine, responseHeaders, out code, out body)) { // does it look a lot like a proxy server? bool isProxy = headers.ContainsKey("via"); if (!isProxy) { foreach (string key in headers.Keys) { if (key.EndsWith("-via")) { isProxy = true; break; } } } if (isProxy) { throw new CloseSocketException(); // no need to log this } if ((failSockets++ % 100) == 0) { // DUMP HEADERS var sb = new StringBuilder("Failing request: ").AppendLine(requestLine); foreach (string key in headers.Keys) { sb.AppendFormat("{0}:\t{1}", key, headers[key]).AppendLine(); } context?.Handler?.ErrorLog?.WriteLine(sb); } throw new InvalidOperationException("Request was not a web-socket upgrade request; connection: " + headers["Connection"] + ", upgrade: " + headers["Upgrade"]); } else { var newProcessor = new BasicHttpResponseProcessor(); connection.SetProtocol(newProcessor); var resp = new StringBuilder(); resp.Append("HTTP/1.0 ").Append((int)code).Append(' ').Append(code).AppendLine(); foreach (string key in responseHeaders.Keys) { string val = responseHeaders[key]; if (string.IsNullOrEmpty(val)) { continue; } resp.Append(key).Append(": ").Append(val).AppendLine(); } resp.Append("Connection: close").AppendLine(); resp.AppendLine(); if (!string.IsNullOrEmpty(body)) { resp.AppendLine(body); } connection.Send(context, resp.ToString()); newProcessor.SendShutdown(context); connection.PromptToSend(context); throw new CloseSocketException(); } } }
protected override int ProcessHeadersAndUpgrade(NetContext context, Connection conn, Stream input, Stream additionalData, int additionalOffset) { string requestLine; var headers = ParseHeaders(input, out requestLine); string host = headers["Host"]; // context.Handler.WriteLog(host + ": " + requestLine, conn); //The "Request-URI" of the GET method [RFC2616] is used to identify the //endpoint of the WebSocket connection, both to allow multiple domains //to be served from one IP address and to allow multiple WebSocket //endpoints to be served by a single server. var connection = (WebSocketConnection) conn; connection.Host = host; connection.Origin = headers["Origin"] ?? headers["Sec-WebSocket-Origin"]; // Some early drafts used the latter, so we'll allow it as a fallback // in particular, two drafts of version "8" used (separately) **both**, // so we can't rely on the version for this (hybi-10 vs hybi-11). // To make it even worse, hybi-00 used Origin, so it is all over the place! connection.Protocol = headers["Sec-WebSocket-Protocol"]; connection.RequestLine = requestLine; if (string.IsNullOrEmpty(connection.Host)) { //4. The request MUST contain a |Host| header field whose value //contains /host/ plus optionally ":" followed by /port/ (when not //using the default port). throw new InvalidOperationException("host required"); } bool looksGoodEnough = false; // mozilla sends "keep-alive, Upgrade"; let's make it more forgiving var connectionParts = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase); if (headers.ContainsKey("Connection")) { // so for mozilla, this will be the set {"keep-alive", "Upgrade"} var parts = headers["Connection"].Split(Comma); foreach (var part in parts) connectionParts.Add(part.Trim()); } if (connectionParts.Contains("Upgrade") && string.Equals(headers["Upgrade"], "websocket", StringComparison.InvariantCultureIgnoreCase)) { //5. The request MUST contain an |Upgrade| header field whose value //MUST include the "websocket" keyword. //6. The request MUST contain a |Connection| header field whose value //MUST include the "Upgrade" token. looksGoodEnough = true; } if(!looksGoodEnough && AllowClientsMissingConnectionHeaders) { if((headers.ContainsKey("Sec-WebSocket-Version") && headers.ContainsKey("Sec-WebSocket-Key")) || (headers.ContainsKey("Sec-WebSocket-Key1") && headers.ContainsKey("Sec-WebSocket-Key2"))) { looksGoodEnough = true; } } if(looksGoodEnough) { //9. The request MUST include a header field with the name //|Sec-WebSocket-Version|. The value of this header field MUST be WebSocketsProcessor newProtocol; string version = headers["Sec-WebSocket-Version"]; byte[] body = null; int bodyRead = 0; bool processExtensions = false; if(version == null) { if (headers.ContainsKey("Sec-WebSocket-Key1") && headers.ContainsKey("Sec-WebSocket-Key2")) { // smells like hixie-76/hybi-00 newProtocol = new WebSocketsProcessor_Hixie76_00.Server(); } else { throw new NotSupportedException(); } } else { switch(version) { case "4": case "5": case "6": case "7": case "8": // these are all early drafts case "13": // this is later drafts and RFC6455 newProtocol = new WebSocketsProcessor_RFC6455_13(false); processExtensions = true; break; default: // should issues a 400 "upgrade required" and specify Sec-WebSocket-Version - see 4.4 throw new InvalidOperationException(string.Format("Sec-WebSocket-Version {0} is not supported", version)); } } //if(headers.ContainsKey("x-forwarded-for")) //{ // context.Handler.WriteLog("forwarded for: " + headers["x-forwarded-for"], conn); //} if (processExtensions) { AddMatchingExtensions(headers, connection, context.Extensions); } connection.HighPriority = false; // regular priority when we've got as far as switching protocols connection.SetProtocol(newProtocol); context.Handler.OnAuthenticate(connection, headers); newProtocol.CompleteHandshake(context, connection, input, requestLine, headers, body); context.Handler.OnAfterAuthenticate(connection); connection.PromptToSend(context); return bodyRead; } else { var responseHeaders = new StringDictionary(); HttpStatusCode code; string body; if (!TryBasicResponse(context, headers, requestLine, responseHeaders, out code, out body)) { // does it look a lot like a proxy server? bool isProxy = headers.ContainsKey("via"); if(!isProxy) { foreach(string key in headers.Keys) { if(key.EndsWith("-via")) { isProxy = true; break; } } } if(isProxy) { throw new CloseSocketException(); // no need to log this } if ((failSockets++ % 100) == 0) { // DUMP HEADERS var sb = new StringBuilder("Failing request: ").AppendLine(requestLine); foreach (string key in headers.Keys) { sb.AppendFormat("{0}:\t{1}", key, headers[key]).AppendLine(); } Console.Error.WriteLine(sb); } throw new InvalidOperationException("Request was not a web-socket upgrade request; connection: " + headers["Connection"] + ", upgrade: " + headers["Upgrade"]); } else { var newProcessor = new BasicHttpResponseProcessor(); connection.SetProtocol(newProcessor); var resp = new StringBuilder(); resp.Append("HTTP/1.0 ").Append((int)code).Append(' ').Append(code).AppendLine(); foreach (string key in responseHeaders.Keys) { string val = responseHeaders[key]; if(string.IsNullOrEmpty(val)) continue; resp.Append(key).Append(": ").Append(val).AppendLine(); } resp.Append("Connection: close").AppendLine(); resp.AppendLine(); if (!string.IsNullOrEmpty(body)) resp.AppendLine(body); connection.Send(context, resp.ToString()); newProcessor.SendShutdown(context); connection.PromptToSend(context); throw new CloseSocketException(); } } }