using System; using System.Buffers; using System.Collections.Generic; using System.Collections.Specialized; using System.Threading; using UMC.Data.Entities; using UMC.Net; namespace UMC.ITME { public class HttpMimeRequest : UMC.Net.MimeRequest, IDisposable { NameValueCollection _Headers = new NameValueCollection(); public NameValueCollection Headers { get { return _Headers; } } public NameValueCollection Cookies { get { return _Cookies; } } NameValueCollection _Cookies = new NameValueCollection(); internal HttpMime _context; public HttpMimeRequest(HttpMime context) { _context = context; this._remoteIpAddress = context.RemoteIpAddress; } public string HttpMethod { get; private set; } public string RawUrl { get; private set; } public string ContentType { get; private set; } Uri _Referer; public Uri UrlReferrer { get { if (_Referer == null) { var referer = _Headers.Get("Referer"); if (String.IsNullOrEmpty(referer) == false) { try { _Referer = new Uri(referer); } catch { _Referer = new Uri(this._uri, "/"); } } else { _Referer = new Uri(this._uri, "/"); } } return _Referer; } } Uri _uri; public Uri Url { get { return this._uri; } } public void RewriteUrl(String pathAndQuery) { this._uri = new Uri(_uri, pathAndQuery); } String _remoteIpAddress; public string UserHostAddress { get { return _remoteIpAddress; } } bool _IsUpgrade, _isWebSocket; public override bool IsWebSocket => _isWebSocket && _IsUpgrade; protected override void Header(byte[] data, int offset, int size) { var utf = System.Text.Encoding.UTF8; var start = offset; var host = ""; var scheme = _context.Scheme; for (var ci = 0; ci < size - 2; ci++) { var index = ci + offset; if (data[index] == 10 && data[index - 1] == 13) { var heaerValue = utf.GetString(data, start, index - start - 1); if (start == offset) { var ls = heaerValue.Split(' '); if (ls.Length == 3) { this.HttpMethod = ls[0]; this.RawUrl = ls[1]; if (ls[2].StartsWith("HTTP/") == false) { this.IsHttpFormatError = true; _context.OutText(400, "Bad Request"); return; } } else { this.IsHttpFormatError = true; _context.OutText(400, "Bad Request"); return; } } else { var vi = heaerValue.IndexOf(':'); var key = heaerValue.Substring(0, vi); var value = heaerValue.Substring(vi + 2); switch (key.ToLower()) { case "x-forwarded-host": host = value; break; case "host": this._Headers.Add(key, value); if (String.IsNullOrEmpty(host)) host = value; break; case "x-real-ip": case "x-forwarded-for": this._remoteIpAddress = value; break; case "x-forwarded-proto": scheme = value; break; case "connection": _IsUpgrade = value.Contains("upgrade", StringComparison.CurrentCultureIgnoreCase); this._Headers.Add(key, value); break; case "upgrade": _isWebSocket = value.Contains("websocket", StringComparison.CurrentCultureIgnoreCase); this._Headers.Add(key, value); break; case "cookie": this._Headers.Add(key, value); int begin = 0; while (begin < value.Length) { var vEnd = value.IndexOf("; ", begin); var vIndex = value.IndexOf('=', begin); if (vEnd > 0) { if (vEnd > vIndex && vIndex > 0) { _Cookies.Add(value.Substring(begin, vIndex - begin), value.Substring(vIndex + 1, vEnd - vIndex - 1)); } else { _Cookies.Add(value.Substring(begin, vEnd - begin), null); } begin = vEnd + 2; } else { if (vIndex > 0) { _Cookies.Add(value.Substring(begin, vIndex - begin), value.Substring(vIndex + 1)); } else { _Cookies.Add(value.Substring(begin), null); } break; } } break; case "content-type": this._Headers.Add(key, value); this.ContentType = value; break; default: this._Headers.Add(key, value); break; } } start = index + 1; } } if (String.IsNullOrEmpty(host)) { host = _context.Host; } var searchIndex = this.RawUrl.IndexOf('?'); var rawUrl = Uri.UnescapeDataString(searchIndex == -1 ? this.RawUrl : this.RawUrl.Substring(0, searchIndex)); for (var i = 2; i < rawUrl.Length; i++) { switch (rawUrl[i]) { case '\u0000': this.IsHttpFormatError = true; _context.OutText(400, "Path Traversal"); return; case '/': if (rawUrl[i - 1] == '.' && rawUrl[i - 2] == '.') { this.IsHttpFormatError = true; _context.OutText(400, "Path Traversal"); return; } break; } } try { this._uri = new Uri($"{scheme}://{host}{this.RawUrl}"); } catch { this.IsHttpFormatError = true; _context.OutText(400, $"Bad Request"); return; } _context.PrepareRespone(this); } byte[] _lastFormBuffer; int lastFormBufferSize = 0; String FormKey; NameValueCollection _from = new NameValueCollection(); public void ReadAsForm(Action action) { if (this.ContentType?.Contains("form-urlencoded", StringComparison.CurrentCultureIgnoreCase) == true) { _lastFormBuffer = new byte[0x100]; this.ReadAsData((b, i, c) => { if (b.Length == 0) { if (i == 0 && c == 0) { this.FormValue(b, i, c); } action(_from); } else { this.FormValue(b, i, c); } }); } else { action(_from); } } public NameValueCollection Form { get { return _from; } } void FormValue(byte[] data, int offset, int size) { for (int i = 0; i < size; i++) { switch (data[offset + i]) { case 0x26: String value = String.Empty; if (lastFormBufferSize > 0) { value = System.Text.Encoding.UTF8.GetString(System.Web.HttpUtility.UrlDecodeToBytes(_lastFormBuffer, 0, lastFormBufferSize)); } if (String.IsNullOrEmpty(FormKey) == false) { _from.Add(FormKey, value); FormKey = String.Empty; } else if (String.IsNullOrEmpty(value) == false) { _from.Add(value, null); } lastFormBufferSize = 0; break; case 0x3d: FormKey = lastFormBufferSize == 0 ? String.Empty : System.Text.Encoding.UTF8.GetString(System.Web.HttpUtility.UrlDecodeToBytes(_lastFormBuffer, 0, lastFormBufferSize)); lastFormBufferSize = 0; break; default: if (lastFormBufferSize == _lastFormBuffer.Length) { var b = new byte[lastFormBufferSize + 0x100]; Array.Copy(_lastFormBuffer, 0, b, 0, lastFormBufferSize); _lastFormBuffer = b; } _lastFormBuffer[lastFormBufferSize] = data[offset + i]; lastFormBufferSize++; break; } } if (offset == 0 && size == 0 && data.Length == 0) { String value = String.Empty; if (lastFormBufferSize > 0) { value = System.Text.Encoding.UTF8.GetString(System.Web.HttpUtility.UrlDecodeToBytes(_lastFormBuffer, 0, lastFormBufferSize)); } if (String.IsNullOrEmpty(FormKey) == false) { _from.Add(FormKey, value); } else if (String.IsNullOrEmpty(value) == false) { _from.Add(value, null); } lastFormBufferSize = 0; } } Queue> _body = new Queue>(); Net.NetWriteData _readData; protected override void Body(byte[] data, int offset, int size) { if (size > 0) { if (_readData != null) { while (this._body.TryDequeue(out var d)) { _readData(d.Item1, 0, d.Item2); ArrayPool.Shared.Return(d.Item1); } _readData(data, offset, size); } else { var d = ArrayPool.Shared.Rent(size); Array.Copy(data, offset, d, 0, size); _body.Enqueue(Tuple.Create(d, size));//bytes = d, size = size }); } } } Exception _Error; protected override void ReceiveError(Exception ex) { _Error = ex; if (_readData != null) { while (this._body.TryDequeue(out var d)) { _readData(d.Item1, 0, d.Item2); ArrayPool.Shared.Return(d.Item1); } _readData(Array.Empty(), -1, 0); } _context.OutputFinish(); } public bool IsReadBody { get; private set; } public void ReadAsData(Net.NetWriteData readData) { if (IsReadBody == false) { IsReadBody = true; //lock (_sysc) { if (this.IsHttpFormatError) { readData(Array.Empty(), -1, 0); } else if (this.isBodyFinish) { while (this._body.TryDequeue(out var d)) { _readData(d.Item1, 0, d.Item2); ArrayPool.Shared.Return(d.Item1); } readData(Array.Empty(), 0, 0); } else { this._readData = readData; } } } else { readData(Array.Empty(), this.IsHttpFormatError ? -1 : 0, 0); } } public override void Finish() { //lock (_sysc) { this.isBodyFinish = true; if (_readData != null) { while (this._body.TryDequeue(out var d)) { _readData(d.Item1, 0, d.Item2); ArrayPool.Shared.Return(d.Item1); } _readData(Array.Empty(), 0, 0); } } } public void Dispose() { while (this._body.TryDequeue(out var d)) { ArrayPool.Shared.Return(d.Item1); } } bool isBodyFinish = false; } }