2017-04-25 18:25:48 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading.Tasks;
|
2017-07-22 19:27:41 +00:00
|
|
|
|
using Glove;
|
2017-04-25 18:25:48 +00:00
|
|
|
|
|
2017-06-16 21:00:01 +00:00
|
|
|
|
namespace Kneesocks {
|
2017-04-27 20:44:30 +00:00
|
|
|
|
public class Handshake {
|
2017-04-25 18:25:48 +00:00
|
|
|
|
private const string HttpVersion = "1.1";
|
2017-05-09 21:08:39 +00:00
|
|
|
|
public bool IsRequest = false;
|
2017-04-25 18:25:48 +00:00
|
|
|
|
|
|
|
|
|
public enum kStatusCode {
|
|
|
|
|
Switching_Protocols = 101,
|
|
|
|
|
|
|
|
|
|
Bad_Request = 400,
|
|
|
|
|
Unauthorized = 401,
|
|
|
|
|
Forbidden = 403,
|
|
|
|
|
Not_Found = 404,
|
|
|
|
|
Method_Not_Allowed = 405,
|
|
|
|
|
Not_Acceptable = 406,
|
|
|
|
|
Request_Timeout = 408,
|
|
|
|
|
Conflict = 409,
|
|
|
|
|
Gone = 410,
|
|
|
|
|
|
|
|
|
|
Internal_Server_Error = 500,
|
|
|
|
|
Not_Implemented = 501,
|
|
|
|
|
Bad_Gateway = 502,
|
|
|
|
|
Service_Unavailable = 503,
|
|
|
|
|
Gateway_Timeout = 504
|
|
|
|
|
}
|
2017-05-09 21:08:39 +00:00
|
|
|
|
public kStatusCode? StatusCode { get; private set; } = null;
|
2017-04-25 18:25:48 +00:00
|
|
|
|
protected string StatusCodeText {
|
2017-05-20 23:33:39 +00:00
|
|
|
|
get => Enum.GetName(typeof(kStatusCode), StatusCode).Replace('_', ' ');
|
2017-04-25 18:25:48 +00:00
|
|
|
|
}
|
2017-05-09 21:08:39 +00:00
|
|
|
|
|
2017-04-25 18:25:48 +00:00
|
|
|
|
private Dictionary<string, string> Headers =
|
|
|
|
|
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
public string Content { get; set; } = null;
|
|
|
|
|
|
|
|
|
|
public Handshake(string rawData) {
|
2017-05-09 21:08:39 +00:00
|
|
|
|
IsRequest = true;
|
|
|
|
|
|
2017-04-25 18:25:48 +00:00
|
|
|
|
var headerLength = rawData.IndexOf("\r\n\r\n");
|
|
|
|
|
if(headerLength == -1)
|
|
|
|
|
throw new FormatException("Header delimeter not found in raw data");
|
|
|
|
|
|
|
|
|
|
var header = rawData.Substring(0, headerLength);
|
2017-05-08 21:06:17 +00:00
|
|
|
|
if(!header.StartsWith("GET ") || !header.Contains("HTTP/"))
|
2017-04-25 18:25:48 +00:00
|
|
|
|
throw new FormatException("Protocol defined in status line not understood");
|
|
|
|
|
|
|
|
|
|
var lines = header.Split('\n');
|
|
|
|
|
foreach(var line in lines) {
|
|
|
|
|
string[] parts;
|
2017-05-08 21:06:17 +00:00
|
|
|
|
if(line.StartsWith("GET ")) {
|
2017-04-25 18:25:48 +00:00
|
|
|
|
parts = line.Trim().Split(' ');
|
2017-05-08 21:06:17 +00:00
|
|
|
|
if(parts.Length < 3)
|
2017-04-25 18:25:48 +00:00
|
|
|
|
throw new FormatException("Status line in header malformed");
|
|
|
|
|
} else {
|
|
|
|
|
parts = line.Trim().Split(new char[] {':'}, 2);
|
|
|
|
|
if(parts.Length == 2)
|
|
|
|
|
Headers.Add(parts[0].Trim(), parts[1].Trim());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(Headers.ContainsKey("Content-Length")) {
|
|
|
|
|
rawData.Substring(headerLength + 4, int.Parse(Headers["Content-Length"]));
|
|
|
|
|
} else {
|
|
|
|
|
if(rawData.Length > headerLength + 4)
|
|
|
|
|
Content = rawData.Substring(headerLength + 4);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Handshake(kStatusCode statusCode = kStatusCode.Switching_Protocols, string content = null) {
|
|
|
|
|
StatusCode = statusCode;
|
|
|
|
|
Content = content;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Handshake AcceptRequest(Handshake request) {
|
|
|
|
|
const string nonce = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
|
|
|
var key = request.GetHeader("Sec-WebSocket-Key");
|
2017-05-09 12:22:04 +00:00
|
|
|
|
var connectionHash = (key + nonce).SHA1().Base64Encode();
|
2017-05-08 21:06:17 +00:00
|
|
|
|
|
2017-04-25 18:25:48 +00:00
|
|
|
|
var shake = new Handshake(kStatusCode.Switching_Protocols);
|
|
|
|
|
shake.SetHeader("Upgrade", "websocket")
|
|
|
|
|
.SetHeader("Connection", "Upgrade")
|
|
|
|
|
.SetHeader("Sec-WebSocket-Accept", connectionHash);
|
|
|
|
|
return shake;
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-20 23:33:39 +00:00
|
|
|
|
public static Handshake DenyRequest(kStatusCode statusCode = kStatusCode.Bad_Request, string message = "Handshake failed.")
|
|
|
|
|
=> new Handshake(statusCode, message);
|
2017-04-25 18:25:48 +00:00
|
|
|
|
|
2017-05-20 23:33:39 +00:00
|
|
|
|
public byte[] ToBytes() => ToString().GetBytes();
|
2017-05-08 21:06:17 +00:00
|
|
|
|
|
2017-05-01 21:04:34 +00:00
|
|
|
|
public override string ToString() {
|
2017-05-09 21:08:39 +00:00
|
|
|
|
if(IsRequest)
|
|
|
|
|
throw new NotSupportedException("Cannot create a request string.");
|
|
|
|
|
|
2017-04-25 18:25:48 +00:00
|
|
|
|
if(Content != null) {
|
2017-05-01 21:04:34 +00:00
|
|
|
|
SetHeader("Content-Length", Content.ByteLength().ToString());
|
2017-04-25 18:25:48 +00:00
|
|
|
|
if(GetHeader("Content-Type") == null)
|
|
|
|
|
SetHeader("Content-Type", "text/plain");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var raw = "HTTP/"+ HttpVersion +" "+ (int)StatusCode + " "+ StatusCodeText +"\r\n";
|
|
|
|
|
foreach(var header in Headers)
|
|
|
|
|
raw += header.Key.Trim() + ": " + header.Value.Trim() + "\r\n";
|
|
|
|
|
return raw += "\r\n";
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-20 23:33:39 +00:00
|
|
|
|
public bool HasHeader(string name) => Headers.ContainsKey(name);
|
2017-05-09 21:08:39 +00:00
|
|
|
|
|
2017-04-25 18:25:48 +00:00
|
|
|
|
public string GetHeader(string name) {
|
|
|
|
|
if(Headers.ContainsKey(name))
|
|
|
|
|
return Headers[name];
|
|
|
|
|
else return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Handshake SetHeader(string name, string value) {
|
|
|
|
|
if(Headers.ContainsKey(name))
|
|
|
|
|
Headers[name] = value;
|
|
|
|
|
else
|
|
|
|
|
Headers.Add(name, value);
|
|
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Handshake AppendContent(string content) {
|
|
|
|
|
Content += content;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-27 20:44:30 +00:00
|
|
|
|
public Handshake ClearContent() {
|
2017-04-25 18:25:48 +00:00
|
|
|
|
Content = null;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|