Unverified Commit b81baeca authored by honfika's avatar honfika Committed by GitHub

Merge pull request #689 from justcoding121/master

beta
parents e05318f4 4f43bd16
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Net; using System.Net;
using System.Net.Security; using System.Net.Security;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
...@@ -10,6 +12,7 @@ using Titanium.Web.Proxy.Exceptions; ...@@ -10,6 +12,7 @@ using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.StreamExtended.Network;
namespace Titanium.Web.Proxy.Examples.Basic namespace Titanium.Web.Proxy.Examples.Basic
{ {
...@@ -22,6 +25,9 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -22,6 +25,9 @@ namespace Titanium.Web.Proxy.Examples.Basic
public ProxyTestController() public ProxyTestController()
{ {
proxyServer = new ProxyServer(); proxyServer = new ProxyServer();
proxyServer.EnableHttp2 = true;
// generate root certificate without storing it in file system // generate root certificate without storing it in file system
//proxyServer.CertificateManager.CreateRootCertificate(false); //proxyServer.CertificateManager.CreateRootCertificate(false);
...@@ -32,11 +38,11 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -32,11 +38,11 @@ namespace Titanium.Web.Proxy.Examples.Basic
{ {
if (exception is ProxyHttpException phex) if (exception is ProxyHttpException phex)
{ {
await writeToConsole(exception.Message + ": " + phex.InnerException?.Message, true); await writeToConsole(exception.Message + ": " + phex.InnerException?.Message, ConsoleColor.Red);
} }
else else
{ {
await writeToConsole(exception.Message, true); await writeToConsole(exception.Message, ConsoleColor.Red);
} }
}; };
proxyServer.ForwardToUpstreamGateway = true; proxyServer.ForwardToUpstreamGateway = true;
...@@ -146,6 +152,38 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -146,6 +152,38 @@ namespace Titanium.Web.Proxy.Examples.Basic
} }
} }
private void WebSocket_DataSent(object sender, DataEventArgs e)
{
var args = (SessionEventArgs)sender;
WebSocketDataSentReceived(args, e, true);
}
private void WebSocket_DataReceived(object sender, DataEventArgs e)
{
var args = (SessionEventArgs)sender;
WebSocketDataSentReceived(args, e, false);
}
private void WebSocketDataSentReceived(SessionEventArgs args, DataEventArgs e, bool sent)
{
var color = sent ? ConsoleColor.Green : ConsoleColor.Blue;
foreach (var frame in args.WebSocketDecoder.Decode(e.Buffer, e.Offset, e.Count))
{
if (frame.OpCode == WebsocketOpCode.Binary)
{
var data = frame.Data.ToArray();
string str = string.Join(",", data.ToArray().Select(x => x.ToString("X2")));
writeToConsole(str, color).Wait();
}
if (frame.OpCode == WebsocketOpCode.Text)
{
writeToConsole(frame.GetText(), color).Wait();
}
}
}
private Task onBeforeTunnelConnectResponse(object sender, TunnelConnectSessionEventArgs e) private Task onBeforeTunnelConnectResponse(object sender, TunnelConnectSessionEventArgs e)
{ {
return Task.FromResult(false); return Task.FromResult(false);
...@@ -205,6 +243,12 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -205,6 +243,12 @@ namespace Titanium.Web.Proxy.Examples.Basic
private async Task onResponse(object sender, SessionEventArgs e) private async Task onResponse(object sender, SessionEventArgs e)
{ {
if (e.HttpClient.ConnectRequest?.TunnelType == TunnelType.Websocket)
{
e.DataSent += WebSocket_DataSent;
e.DataReceived += WebSocket_DataReceived;
}
await writeToConsole("Active Server Connections:" + ((ProxyServer)sender).ServerConnectionCount); await writeToConsole("Active Server Connections:" + ((ProxyServer)sender).ServerConnectionCount);
string ext = System.IO.Path.GetExtension(e.HttpClient.Request.RequestUri.AbsolutePath); string ext = System.IO.Path.GetExtension(e.HttpClient.Request.RequestUri.AbsolutePath);
...@@ -277,14 +321,14 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -277,14 +321,14 @@ namespace Titanium.Web.Proxy.Examples.Basic
return Task.FromResult(0); return Task.FromResult(0);
} }
private async Task writeToConsole(string message, bool useRedColor = false) private async Task writeToConsole(string message, ConsoleColor? consoleColor = null)
{ {
await @lock.WaitAsync(); await @lock.WaitAsync();
if (useRedColor) if (consoleColor.HasValue)
{ {
ConsoleColor existing = Console.ForegroundColor; ConsoleColor existing = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red; Console.ForegroundColor = consoleColor.Value;
Console.WriteLine(message); Console.WriteLine(message);
Console.ForegroundColor = existing; Console.ForegroundColor = existing;
} }
......
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/CodeAnnotations/NamespacesWithAnnotations/=Titanium_002EWeb_002EProxy_002EExamples_002EWpf_002EAnnotations/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/CodeAnnotations/NamespacesWithAnnotations/=Titanium_002EWeb_002EProxy_002EExamples_002EWpf_002EAnnotations/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/CodeInspection/GeneratedCode/GeneratedFileMasks/=docfx_002Ejson/@EntryIndexedValue">docfx.json</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_EMBEDDED_ARRANGEMENT/@EntryValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_EMBEDDED_ARRANGEMENT/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/LINE_FEED_AT_FILE_END/@EntryValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/LINE_FEED_AT_FILE_END/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
......
...@@ -26,6 +26,8 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -26,6 +26,8 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
private bool reRequest; private bool reRequest;
private WebSocketDecoder webSocketDecoder;
/// <summary> /// <summary>
/// Is this session a HTTP/2 promise? /// Is this session a HTTP/2 promise?
/// </summary> /// </summary>
...@@ -58,6 +60,8 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -58,6 +60,8 @@ namespace Titanium.Web.Proxy.EventArguments
} }
} }
public WebSocketDecoder WebSocketDecoder => webSocketDecoder ??= new WebSocketDecoder(BufferPool);
/// <summary> /// <summary>
/// Occurs when multipart request part sent. /// Occurs when multipart request part sent.
/// </summary> /// </summary>
......
...@@ -47,9 +47,15 @@ namespace Titanium.Web.Proxy ...@@ -47,9 +47,15 @@ namespace Titanium.Web.Proxy
try try
{ {
TunnelConnectSessionEventArgs? connectArgs = null; TunnelConnectSessionEventArgs? connectArgs = null;
var method = await HttpHelper.GetMethod(clientStream, BufferPool, cancellationToken);
if (clientStream.IsClosed)
{
return;
}
// Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request) // Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request)
if (await HttpHelper.IsConnectMethod(clientStream, BufferPool, cancellationToken) == 1) if (method == KnownMethod.Connect)
{ {
// read the first line HTTP command // read the first line HTTP command
var requestLine = await clientStream.ReadRequestLine(cancellationToken); var requestLine = await clientStream.ReadRequestLine(cancellationToken);
...@@ -75,6 +81,7 @@ namespace Titanium.Web.Proxy ...@@ -75,6 +81,7 @@ namespace Titanium.Web.Proxy
// filter out excluded host names // filter out excluded host names
bool decryptSsl = endPoint.DecryptSsl && connectArgs.DecryptSsl; bool decryptSsl = endPoint.DecryptSsl && connectArgs.DecryptSsl;
bool sendRawData = !decryptSsl;
if (connectArgs.DenyConnect) if (connectArgs.DenyConnect)
{ {
...@@ -113,6 +120,10 @@ namespace Titanium.Web.Proxy ...@@ -113,6 +120,10 @@ namespace Titanium.Web.Proxy
await clientStream.WriteResponseAsync(response, cancellationToken); await clientStream.WriteResponseAsync(response, cancellationToken);
var clientHelloInfo = await SslTools.PeekClientHello(clientStream, BufferPool, cancellationToken); var clientHelloInfo = await SslTools.PeekClientHello(clientStream, BufferPool, cancellationToken);
if (clientStream.IsClosed)
{
return;
}
bool isClientHello = clientHelloInfo != null; bool isClientHello = clientHelloInfo != null;
if (clientHelloInfo != null) if (clientHelloInfo != null)
...@@ -224,31 +235,41 @@ namespace Titanium.Web.Proxy ...@@ -224,31 +235,41 @@ namespace Titanium.Web.Proxy
$"Couldn't authenticate host '{connectHostname}' with certificate '{certName}'.", e, connectArgs); $"Couldn't authenticate host '{connectHostname}' with certificate '{certName}'.", e, connectArgs);
} }
if (await HttpHelper.IsConnectMethod(clientStream, BufferPool, cancellationToken) == -1) method = await HttpHelper.GetMethod(clientStream, BufferPool, cancellationToken);
if (clientStream.IsClosed)
{ {
decryptSsl = false; return;
} }
if (!decryptSsl) if (method == KnownMethod.Invalid)
{ {
sendRawData = true;
await tcpConnectionFactory.Release(prefetchConnectionTask, true); await tcpConnectionFactory.Release(prefetchConnectionTask, true);
prefetchConnectionTask = null; prefetchConnectionTask = null;
} }
} }
else if (clientHelloInfo == null)
{
method = await HttpHelper.GetMethod(clientStream, BufferPool, cancellationToken);
if (clientStream.IsClosed)
{
return;
}
}
if (cancellationTokenSource.IsCancellationRequested) if (cancellationTokenSource.IsCancellationRequested)
{ {
throw new Exception("Session was terminated by user."); throw new Exception("Session was terminated by user.");
} }
// Hostname is excluded or it is not an HTTPS connect if (method == KnownMethod.Invalid)
if (!decryptSsl || !isClientHello)
{ {
if (!isClientHello) sendRawData = true;
{ }
connectRequest.TunnelType = TunnelType.Websocket;
}
// Hostname is excluded or it is not an HTTPS connect
if (sendRawData)
{
// create new connection to server. // create new connection to server.
// If we detected that client tunnel CONNECTs without SSL by checking for empty client hello then // If we detected that client tunnel CONNECTs without SSL by checking for empty client hello then
// this connection should not be HTTPS. // this connection should not be HTTPS.
...@@ -302,7 +323,7 @@ namespace Titanium.Web.Proxy ...@@ -302,7 +323,7 @@ namespace Titanium.Web.Proxy
} }
} }
if (connectArgs != null && await HttpHelper.IsPriMethod(clientStream, BufferPool, cancellationToken) == 1) if (connectArgs != null && method == KnownMethod.Pri)
{ {
// todo // todo
string? httpCmd = await clientStream.ReadLineAsync(cancellationToken); string? httpCmd = await clientStream.ReadLineAsync(cancellationToken);
......
...@@ -167,32 +167,11 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -167,32 +167,11 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Determines whether is connect method. /// Gets the HTTP method from the stream.
/// </summary> /// </summary>
/// <returns>1: when CONNECT, 0: when valid HTTP method, -1: otherwise</returns> public static async ValueTask<KnownMethod> GetMethod(IPeekStream httpReader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
internal static ValueTask<int> IsConnectMethod(IPeekStream httpReader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
{ {
return startsWith(httpReader, bufferPool, "CONNECT", cancellationToken); const int lengthToCheck = 20;
}
/// <summary>
/// Determines whether is pri method (HTTP/2).
/// </summary>
/// <returns>1: when PRI, 0: when valid HTTP method, -1: otherwise</returns>
internal static ValueTask<int> IsPriMethod(IPeekStream httpReader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
{
return startsWith(httpReader, bufferPool, "PRI", cancellationToken);
}
/// <summary>
/// Determines whether the stream starts with the given string.
/// </summary>
/// <returns>
/// 1: when starts with the given string, 0: when valid HTTP method, -1: otherwise
/// </returns>
private static async ValueTask<int> startsWith(IPeekStream httpReader, IBufferPool bufferPool, string expectedStart, CancellationToken cancellationToken = default)
{
const int lengthToCheck = 10;
if (bufferPool.BufferSize < lengthToCheck) if (bufferPool.BufferSize < lengthToCheck)
{ {
throw new Exception($"Buffer is too small. Minimum size is {lengthToCheck} bytes"); throw new Exception($"Buffer is too small. Minimum size is {lengthToCheck} bytes");
...@@ -201,13 +180,12 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -201,13 +180,12 @@ namespace Titanium.Web.Proxy.Helpers
byte[] buffer = bufferPool.GetBuffer(bufferPool.BufferSize); byte[] buffer = bufferPool.GetBuffer(bufferPool.BufferSize);
try try
{ {
bool isExpected = true;
int i = 0; int i = 0;
while (i < lengthToCheck) while (i < lengthToCheck)
{ {
int peeked = await httpReader.PeekBytesAsync(buffer, i, i, lengthToCheck - i, cancellationToken); int peeked = await httpReader.PeekBytesAsync(buffer, i, i, lengthToCheck - i, cancellationToken);
if (peeked <= 0) if (peeked <= 0)
return -1; return KnownMethod.Invalid;
peeked += i; peeked += i;
...@@ -216,27 +194,94 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -216,27 +194,94 @@ namespace Titanium.Web.Proxy.Helpers
int b = buffer[i]; int b = buffer[i];
if (b == ' ' && i > 2) if (b == ' ' && i > 2)
return isExpected ? 1 : 0; return getKnownMethod(buffer.AsSpan(0, i));
else
{ char ch = (char)b;
char ch = (char)b; if ((ch < 'A' || ch > 'z' || (ch > 'Z' && ch < 'a')) && (ch != '-')) // ASCII letter
if (ch < 'A' || ch > 'z' || (ch > 'Z' && ch < 'a')) // ASCII letter return KnownMethod.Invalid;
return -1;
else if (i >= expectedStart.Length || ch != expectedStart[i])
isExpected = false;
}
i++; i++;
} }
} }
// only letters // only letters, but no space (or shorter than 3 characters)
return 0; return KnownMethod.Invalid;
} }
finally finally
{ {
bufferPool.ReturnBuffer(buffer); bufferPool.ReturnBuffer(buffer);
} }
} }
private static KnownMethod getKnownMethod(ReadOnlySpan<byte> method)
{
// the following methods are supported:
// Connect
// Delete
// Get
// Head
// Options
// Post
// Put
// Trace
// Pri
// method parameter should have at least 3 bytes
byte b1 = method[0];
byte b2 = method[1];
byte b3 = method[2];
switch (method.Length)
{
case 3:
// Get or Put
if (b1 == 'G')
return b2 == 'E' && b3 == 'T' ? KnownMethod.Get : KnownMethod.Unknown;
if (b1 == 'P')
{
if (b2 == 'U')
return b3 == 'T' ? KnownMethod.Put : KnownMethod.Unknown;
if (b2 == 'R')
return b3 == 'I' ? KnownMethod.Pri : KnownMethod.Unknown;
}
break;
case 4:
// Head or Post
if (b1 == 'H')
return b2 == 'E' && b3 == 'A' && method[3] == 'D' ? KnownMethod.Head : KnownMethod.Unknown;
if (b1 == 'P')
return b2 == 'O' && b3 == 'S' && method[3] == 'T' ? KnownMethod.Post : KnownMethod.Unknown;
break;
case 5:
// Trace
if (b1 == 'T')
return b2 == 'R' && b3 == 'A' && method[3] == 'C' && method[4] == 'E' ? KnownMethod.Trace : KnownMethod.Unknown;
break;
case 6:
// Delete
if (b1 == 'D')
return b2 == 'E' && b3 == 'L' && method[3] == 'E' && method[4] == 'T' && method[5] == 'E' ? KnownMethod.Delete : KnownMethod.Unknown;
break;
case 7:
// Connect or Options
if (b1 == 'C')
return b2 == 'O' && b3 == 'N' && method[3] == 'N' && method[4] == 'E' && method[5] == 'C' && method[6] == 'T' ? KnownMethod.Connect : KnownMethod.Unknown;
if (b1 == 'O')
return b2 == 'P' && b3 == 'T' && method[3] == 'I' && method[4] == 'O' && method[5] == 'N' && method[6] == 'S' ? KnownMethod.Options : KnownMethod.Unknown;
break;
}
return KnownMethod.Unknown;
}
} }
} }
...@@ -650,7 +650,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -650,7 +650,7 @@ namespace Titanium.Web.Proxy.Helpers
if (bufferDataLength == buffer.Length) if (bufferDataLength == buffer.Length)
{ {
resizeBuffer(ref buffer, bufferDataLength * 2); Array.Resize(ref buffer, bufferDataLength * 2);
} }
} }
} }
...@@ -678,18 +678,6 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -678,18 +678,6 @@ namespace Titanium.Web.Proxy.Helpers
} }
} }
/// <summary>
/// Increase size of buffer and copy existing content to new buffer
/// </summary>
/// <param name="buffer"></param>
/// <param name="size"></param>
private static void resizeBuffer(ref byte[] buffer, long size)
{
var newBuffer = new byte[size];
Buffer.BlockCopy(buffer, 0, newBuffer, 0, buffer.Length);
buffer = newBuffer;
}
/// <summary> /// <summary>
/// Base Stream.BeginRead will call this.Read and block thread (we don't want this, Network stream handles async) /// Base Stream.BeginRead will call this.Read and block thread (we don't want this, Network stream handles async)
/// In order to really async Reading Launch this.ReadAsync as Task will fire NetworkStream.ReadAsync /// In order to really async Reading Launch this.ReadAsync as Task will fire NetworkStream.ReadAsync
......
namespace Titanium.Web.Proxy.Helpers
{
internal enum KnownMethod
{
Unknown,
Invalid,
// RFC 7231: Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
Connect,
Delete,
Get,
Head,
Options,
Post,
Put,
Trace,
// RFC 7540: Hypertext Transfer Protocol Version 2
Pri,
// RFC 5789: PATCH Method for HTTP
Patch,
// RFC 3744: Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol
Acl,
// RFC 3253: Versioning Extensions to WebDAV (Web Distributed Authoring and Versioning)
BaselineControl,
Checkin,
Checkout,
Label,
Merge,
Mkactivity,
Mkworkspace,
Report,
Unckeckout,
Update,
VersionControl,
// RFC 3648: Web Distributed Authoring and Versioning (WebDAV) Ordered Collections Protocol
Orderpatch,
// RFC 4437: Web Distributed Authoring and Versioning (WebDAV): Redirect Reference Resources
Mkredirectref,
Updateredirectref,
// RFC 4791: Calendaring Extensions to WebDAV (CalDAV)
Mkcalendar,
// RFC 4918: HTTP Extensions for Web Distributed Authoring and Versioning (WebDAV)
Copy,
Lock,
Mkcol,
Move,
Propfind,
Proppatch,
Unlock,
// RFC 5323: Web Distributed Authoring and Versioning (WebDAV) SEARCH
Search,
// RFC 5842: Binding Extensions to Web Distributed Authoring and Versioning (WebDAV)
Bind,
Rebind,
Unbind,
// Internet Draft snell-link-method: HTTP Link and Unlink Methods
Link,
Unlink,
}
}
...@@ -60,6 +60,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -60,6 +60,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
cacheKeyBuilder.Append("-"); cacheKeyBuilder.Append("-");
cacheKeyBuilder.Append(remotePort); cacheKeyBuilder.Append(remotePort);
cacheKeyBuilder.Append("-"); cacheKeyBuilder.Append("-");
// when creating Tcp client isConnect won't matter // when creating Tcp client isConnect won't matter
cacheKeyBuilder.Append(isHttps); cacheKeyBuilder.Append(isHttps);
...@@ -408,6 +409,7 @@ retry: ...@@ -408,6 +409,7 @@ retry:
continue; continue;
} }
break; break;
} }
catch (Exception e) catch (Exception e)
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Titanium.Web.Proxy.StreamExtended.BufferPool;
namespace Titanium.Web.Proxy
{
public class WebSocketDecoder
{
private byte[] buffer;
private long bufferLength;
internal WebSocketDecoder(IBufferPool bufferPool)
{
buffer = new byte[bufferPool.BufferSize];
}
public IEnumerable<WebSocketFrame> Decode(byte[] data, int offset, int count)
{
var buffer = data.AsMemory(offset, count);
bool copied = false;
if (bufferLength > 0)
{
// already have remaining data
buffer = copyToBuffer(buffer);
copied = true;
}
while (true)
{
var data1 = buffer.Span;
if (!isDataEnough(data1))
{
break;
}
var opCode = (WebsocketOpCode)(data1[0] & 0xf);
bool isFinal = (data1[0] & 0x80) != 0;
byte b = data1[1];
long size = b & 0x7f;
// todo: size > int.Max??
bool masked = (b & 0x80) != 0;
int idx = 2;
if (size > 125)
{
if (size == 126)
{
size = (data1[2] << 8) + data1[3];
idx = 4;
}
else
{
size = ((long)data1[2] << 56) + ((long)data1[3] << 48) + ((long)data1[4] << 40) + ((long)data1[5] << 32) +
((long)data1[6] << 24) + (data1[7] << 16) + (data1[8] << 8) + data1[9];
idx = 10;
}
}
if (data1.Length < idx + size)
{
break;
}
if (masked)
{
//mask = (uint)(((long)data1[idx++] << 24) + (data1[idx++] << 16) + (data1[idx++] << 8) + data1[idx++]);
//mask = (uint)(data1[idx++] + (data1[idx++] << 8) + (data1[idx++] << 16) + ((long)data1[idx++] << 24));
var uData = MemoryMarshal.Cast<byte, uint>(data1.Slice(idx, (int)size + 4));
idx += 4;
uint mask = uData[0];
long size1 = size;
if (size > 4)
{
uData = uData.Slice(1);
for (int i = 0; i < uData.Length; i++)
{
uData[i] = uData[i] ^ mask;
}
size1 -= uData.Length * 4;
}
if (size1 > 0)
{
int pos = (int)(idx + size - size1);
data1[pos] ^= (byte)mask;
if (size1 > 1)
{
data1[pos + 1] ^= (byte)(mask >> 8);
}
if (size1 > 2)
{
data1[pos + 2] ^= (byte)(mask >> 16);
}
; }
}
var frameData = buffer.Slice(idx, (int)size);
var frame = new WebSocketFrame { IsFinal = isFinal, Data = frameData, OpCode = opCode };
yield return frame;
buffer = buffer.Slice((int)(idx + size));
}
if (!copied && buffer.Length > 0)
{
copyToBuffer(buffer);
}
}
private Memory<byte> copyToBuffer(ReadOnlyMemory<byte> data)
{
long requiredLength = bufferLength + data.Length;
if (requiredLength > buffer.Length)
{
Array.Resize(ref buffer, (int)Math.Min(requiredLength, buffer.Length * 2));
}
data.CopyTo(buffer.AsMemory((int)bufferLength));
bufferLength += data.Length;
return buffer.AsMemory(0, (int)bufferLength);
}
private static bool isDataEnough(ReadOnlySpan<byte> data)
{
int length = data.Length;
if (length < 2)
return false;
byte size = data[1];
if ((size & 0x80) != 0) // masked
length -= 4;
size &= 0x7f;
if (size == 126)
{
if (length < 2)
{
return false;
}
}
else if (size == 127)
{
if (length < 10)
{
return false;
}
}
return length >= size;
}
}
}
using System;
using System.Text;
namespace Titanium.Web.Proxy
{
public class WebSocketFrame
{
public bool IsFinal { get; internal set; }
public WebsocketOpCode OpCode { get; internal set; }
public ReadOnlyMemory<byte> Data { get; internal set; }
public string GetText()
{
return GetText(Encoding.UTF8);
}
public string GetText(Encoding encoding)
{
#if NETSTANDARD2_1
return encoding.GetString(Data.Span);
#else
return encoding.GetString(Data.ToArray());
#endif
}
}
}
namespace Titanium.Web.Proxy
{
public enum WebsocketOpCode : byte
{
Continuation,
Text,
Binary,
ConnectionClose = 8,
Ping,
Pong,
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment