You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Gateway/Mime/HttpMimeRequest.cs

478 lines
16 KiB
C#

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<NameValueCollection> 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<Tuple<byte[], int>> _body = new Queue<Tuple<byte[], int>>();
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<byte>.Shared.Return(d.Item1);
}
_readData(data, offset, size);
}
else
{
var d = ArrayPool<byte>.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<byte>.Shared.Return(d.Item1);
}
_readData(Array.Empty<byte>(), -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<byte>(), -1, 0);
}
else if (this.isBodyFinish)
{
while (this._body.TryDequeue(out var d))
{
_readData(d.Item1, 0, d.Item2);
ArrayPool<byte>.Shared.Return(d.Item1);
}
readData(Array.Empty<byte>(), 0, 0);
}
else
{
this._readData = readData;
}
}
}
else
{
readData(Array.Empty<byte>(), 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<byte>.Shared.Return(d.Item1);
}
_readData(Array.Empty<byte>(), 0, 0);
}
}
}
public void Dispose()
{
while (this._body.TryDequeue(out var d))
{
ArrayPool<byte>.Shared.Return(d.Item1);
}
}
bool isBodyFinish = false;
}
}