using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Threading; namespace Maki.Rest { public class WebRequest : IDisposable { private const string USER_AGENT = @"DiscordBot (https://github.com/flashwave/maki, 1.0.0.0)"; private const string GENERIC_CONTENT_TYPE = @"application/octet-stream"; private const string JSON_CONTENT_TYPE = @"application/json"; private const string FORM_CONTENT_TYPE = @"multipart/form-data"; private const long BUFFER_SIZE = 8192000; public readonly HttpMethod Method; public readonly string Url; public string UserAgent { get; set; } = USER_AGENT; public string ContentType { get; set; } = GENERIC_CONTENT_TYPE; public long ContentLength => HttpWebResponse.ContentLength < 1 ? BUFFER_SIZE : HttpWebResponse.ContentLength; // TODO: make this not static internal static string Authorisation { get; set; } private readonly Dictionary Headers = new Dictionary(); private readonly Dictionary Parameters = new Dictionary(); private readonly Dictionary Files = new Dictionary(); private readonly Dictionary MimeTypes = new Dictionary() { { "png", "image/png" }, { "jpg", "image/jpeg" }, { "jpeg", "image/jpeg" }, { "gif", "image/gif" }, }; private byte[] RawRequestBody = new byte[0]; private HttpWebRequest HttpWebRequest; private Stream RequestStream; private HttpWebResponse HttpWebResponse; private Stream ResponseStream; private byte[] RawResponseValue; public byte[] RawResponse { get { if (RawResponseValue == null) { using (MemoryStream ms = new MemoryStream()) { byte[] bytes = new byte[4096]; int read = 0; while ((read = ResponseStream.Read(bytes, 0, bytes.Length)) > 0) ms.Write(bytes, 0, read); ms.Seek(0, SeekOrigin.Begin); RawResponseValue = new byte[ms.Length]; ms.Read(RawResponseValue, 0, RawResponseValue.Length); } } return RawResponseValue; } } private string ResponseString = string.Empty; public string Response { get { if (string.IsNullOrEmpty(ResponseString)) ResponseString = Encoding.UTF8.GetString(RawResponse); return ResponseString; } } public T ResponseJson() => JsonConvert.DeserializeObject(Response); public short Status => (short)HttpWebResponse?.StatusCode; static WebRequest() { ServicePointManager.Expect100Continue = false; } public WebRequest(HttpMethod method, string url) { Method = method; Url = url; } public void AddRaw(byte[] bytes) => RawRequestBody = bytes; public void AddRaw(string str) => AddRaw(Encoding.UTF8.GetBytes(str)); public void AddJson(object obj) { ContentType = JSON_CONTENT_TYPE; AddRaw(JsonConvert.SerializeObject(obj)); } public void AddParam(string name, string contents) => Parameters.Add(name, contents); public void AddFile(string name, byte[] bytes) => Files.Add(name, bytes); public void Perform() { StringBuilder urlBuilder = new StringBuilder(); if (!Url.StartsWith("http://") && !Url.StartsWith("https://")) { urlBuilder.Append(RestEndpoints.BASE_URL); urlBuilder.Append(RestEndpoints.BASE_PATH); } urlBuilder.Append(Url); if (Method == HttpMethod.GET || Method == HttpMethod.DELETE) if (Parameters.Count > 1) { if (!Url.Contains('?')) urlBuilder.Append(@"?"); foreach (KeyValuePair param in Parameters) urlBuilder.Append($@"{param.Key}={param.Value}&"); } string url = urlBuilder.ToString().TrimEnd('&'); HttpWebRequest = System.Net.WebRequest.Create(url) as HttpWebRequest; HttpWebRequest.Method = Method.ToString(); HttpWebRequest.UserAgent = UserAgent; HttpWebRequest.KeepAlive = true; //wRequest.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; HttpWebRequest.ReadWriteTimeout = Timeout.Infinite; HttpWebRequest.Timeout = Timeout.Infinite; if (!string.IsNullOrEmpty(Authorisation) && url.StartsWith(RestEndpoints.BASE_URL + RestEndpoints.BASE_PATH)) HttpWebRequest.Headers[HttpRequestHeader.Authorization] = Authorisation; foreach (KeyValuePair header in Headers) HttpWebRequest.Headers[header.Key] = header.Value; if (Method == HttpMethod.POST || Method == HttpMethod.PUT || Method == HttpMethod.PATCH) { RequestStream = HttpWebRequest.GetRequestStream(); if (Parameters.Count + Files.Count < 1) RequestStream.Write(RawRequestBody, 0, RawRequestBody.Length); else { string boundary = $@"-----------------------------{DateTime.Now.Ticks}"; ContentType = $@"{FORM_CONTENT_TYPE}; boundary={boundary}"; if (Parameters.Count >= 1) { StringBuilder postBodyBuilder = new StringBuilder(); byte[] postBody = new byte[0]; foreach (KeyValuePair param in Parameters) { postBodyBuilder.AppendLine($@"--{boundary}"); postBodyBuilder.AppendLine($@"Content-Disposition: form-data; name=""{param.Key}"""); postBodyBuilder.AppendLine(); postBodyBuilder.AppendLine(param.Value); } postBody = Encoding.UTF8.GetBytes(postBodyBuilder.ToString()); RequestStream.Write(postBody, 0, postBody.Length); } if (Files.Count >= 1) { byte[] boundaryBytes = Encoding.UTF8.GetBytes($@"--{boundary}"); byte[] newLineBytes = Encoding.UTF8.GetBytes("\r\n"); foreach (KeyValuePair file in Files) { string cType = GENERIC_CONTENT_TYPE; string fileExt = Path.GetExtension(file.Key).ToLower().TrimStart('.'); if (MimeTypes.ContainsKey(fileExt)) cType = MimeTypes[fileExt]; byte[] cDisposBytes = Encoding.UTF8.GetBytes($@"Content-Disposition: form-data; name=""{file.Key}""; filename=""{file.Key}"""); byte[] cTypeBytes = Encoding.UTF8.GetBytes($@"Content-Type: {cType}"); // Boundary + newline RequestStream.Write(boundaryBytes, 0, boundaryBytes.Length); RequestStream.Write(newLineBytes, 0, newLineBytes.Length); // Disposition header + newline RequestStream.Write(cDisposBytes, 0, cDisposBytes.Length); RequestStream.Write(newLineBytes, 0, newLineBytes.Length); // Type header + newline RequestStream.Write(cTypeBytes, 0, cTypeBytes.Length); RequestStream.Write(newLineBytes, 0, newLineBytes.Length); // newline + contents + newline RequestStream.Write(newLineBytes, 0, newLineBytes.Length); RequestStream.Write(file.Value, 0, file.Value.Length); RequestStream.Write(newLineBytes, 0, newLineBytes.Length); } } byte[] closingBound = Encoding.UTF8.GetBytes($@"--{boundary}--"); RequestStream.Write(closingBound, 0, closingBound.Length); } } HttpWebRequest.ContentType = ContentType; try { HttpWebResponse = HttpWebRequest.GetResponse() as HttpWebResponse; } catch (WebException ex) { HttpWebResponse = ex.Response as HttpWebResponse; } ResponseStream = HttpWebResponse.GetResponseStream(); } #region IDisposable private bool IsDisposed = false; /// /// Disconnects and releases all unmanaged objects /// private void Dispose(bool disposing) { if (IsDisposed) return; IsDisposed = true; RequestStream?.Dispose(); HttpWebRequest?.Abort(); ResponseStream?.Dispose(); HttpWebResponse?.Close(); if (disposing) GC.SuppressFinalize(true); } ~WebRequest() => Dispose(false); public void Dispose() => Dispose(true); #endregion } }