mirror of https://gitee.com/apiumc/Gateway.git
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.
478 lines
16 KiB
C#
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;
|
|
}
|
|
}
|