using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using System.Collections.Specialized; using System.Net.Sockets; using MinecraftClient.Proxy; using System.Net.Security; using System.Security.Authentication; namespace MinecraftClient.Protocol { /// /// Create a new http request and optionally with proxy according to setting /// public class ProxiedWebRequest { private readonly string httpVersion = "HTTP/1.0"; // Use 1.0 here because 1.1 server may send chunked data private Uri uri; private string host { get { return uri.Host; } } private int port { get { return uri.Port; } } private string path { get { return uri.PathAndQuery; } } private bool isSecure { get { return uri.Scheme == "https"; } } public NameValueCollection Headers = new NameValueCollection(); public string UserAgent { get { return Headers.Get("User-Agent"); } set { Headers.Set("User-Agent", value); } } public string Accept { get { return Headers.Get("Accept"); } set { Headers.Set("Accept", value); } } public string Cookie { set { Headers.Set("Cookie", value); } } /// /// Create a new http request /// /// Target URL public ProxiedWebRequest(string url) { uri = new Uri(url); SetupBasicHeaders(); } /// /// Create a new http request with cookies /// /// Target URL /// Cookies to use public ProxiedWebRequest(string url, NameValueCollection cookies) { uri = new Uri(url); Headers.Add("Cookie", GetCookieString(cookies)); SetupBasicHeaders(); } /// /// Setup some basic headers /// private void SetupBasicHeaders() { Headers.Add("Host", host); Headers.Add("User-Agent", "MCC/" + Program.Version); Headers.Add("Accept", "*/*"); Headers.Add("Connection", "close"); } /// /// Perform GET request and get the response. Proxy is handled automatically /// /// public Response Get() { return Send("GET"); } /// /// Perform POST request and get the response. Proxy is handled automatically /// /// The content type of request body /// Request body /// public Response Post(string contentType, string body) { Headers.Add("Content-Type", contentType); // Calculate length Headers.Add("Content-Length", Encoding.UTF8.GetBytes(body).Length.ToString()); return Send("POST", body); } /// /// Send a http request to the server. Proxy is handled automatically /// /// Method in string representation /// Optional request body /// private Response Send(string method, string body = "") { List requestMessage = new List() { string.Format("{0} {1} {2}", method.ToUpper(), path, httpVersion) // Request line }; foreach (string key in Headers) // Headers { var value = Headers[key]; requestMessage.Add(string.Format("{0}: {1}", key, value)); } requestMessage.Add(""); // if (body != "") { requestMessage.Add(body); } else requestMessage.Add(""); // if (Settings.DebugMessages) { foreach (string l in requestMessage) { ConsoleIO.WriteLine("< " + l); } } Response response = Response.Empty(); AutoTimeout.Perform(() => { TcpClient client = ProxyHandler.newTcpClient(host, port, true); Stream stream; if (isSecure) { stream = new SslStream(client.GetStream()); ((SslStream)stream).AuthenticateAsClient(host, null, SslProtocols.Tls12, true); // Enable TLS 1.2. Hotfix for #1774 } else { stream = client.GetStream(); } string h = string.Join("\r\n", requestMessage.ToArray()); byte[] data = Encoding.ASCII.GetBytes(h); stream.Write(data, 0, data.Length); stream.Flush(); StreamReader sr = new StreamReader(stream); string rawResult = sr.ReadToEnd(); response = ParseResponse(rawResult); try { sr.Close(); stream.Close(); client.Close(); } catch { } }, TimeSpan.FromSeconds(30)); return response; } /// /// Parse a raw response string to response object /// /// raw response string /// private Response ParseResponse(string raw) { int statusCode; string responseBody = ""; NameValueCollection headers = new NameValueCollection(); NameValueCollection cookies = new NameValueCollection(); if (raw.StartsWith("HTTP/1.1") || raw.StartsWith("HTTP/1.0")) { Queue msg = new Queue(raw.Split(new string[] { "\r\n" }, StringSplitOptions.None)); statusCode = int.Parse(msg.Dequeue().Split(' ')[1]); while (msg.Peek() != "") { string[] header = msg.Dequeue().Split(new char[] { ':' }, 2); // Split first ':' only string key = header[0].ToLower(); // Key is case-insensitive string value = header[1]; if (key == "set-cookie") { string[] cookie = value.Split(';'); // cookie options are ignored string[] tmp = cookie[0].Split(new char[] { '=' }, 2); // Split first '=' only string cname = tmp[0].Trim(); string cvalue = tmp[1].Trim(); cookies.Add(cname, cvalue); } else { headers.Add(key, value.Trim()); } } msg.Dequeue(); if (msg.Count > 0) responseBody = msg.Dequeue(); return new Response() { StatusCode = statusCode, Body = responseBody, Headers = headers, Cookies = cookies }; } else { return new Response() { StatusCode = 520, // 502 - Web Server Returned an Unknown Error Body = "", Headers = headers, Cookies = cookies }; } } /// /// Get the cookie string representation to use in header /// /// /// private static string GetCookieString(NameValueCollection cookies) { var sb = new StringBuilder(); foreach (string key in cookies) { var value = cookies[key]; sb.Append(string.Format("{0}={1}; ", key, value)); } string result = sb.ToString(); return result.Remove(result.Length - 2); // Remove "; " at the end } /// /// Basic response object /// public class Response { public int StatusCode; public string Body; public NameValueCollection Headers; public NameValueCollection Cookies; /// /// Get an empty response object /// /// public static Response Empty() { return new Response() { StatusCode = 204, // 204 - No content Body = "", Headers = new NameValueCollection(), Cookies = new NameValueCollection() }; } public override string ToString() { var sb = new StringBuilder(); sb.AppendLine("Status code: " + StatusCode); sb.AppendLine("Headers:"); foreach (string key in Headers) { sb.AppendLine(string.Format(" {0}: {1}", key, Headers[key])); } if (Cookies.Count > 0) { sb.AppendLine(); sb.AppendLine("Cookies: "); foreach (string key in Cookies) { sb.AppendLine(string.Format(" {0}={1}", key, Cookies[key])); } } if (Body != "") { sb.AppendLine(); if (Body.Length > 200) { sb.AppendLine("Body: (Truncated to 200 characters)"); } else sb.AppendLine("Body: "); sb.AppendLine(Body.Length > 200 ? Body.Substring(0, 200) + "..." : Body); } return sb.ToString(); } } } }