Commit b37d7c8f authored by Honfika's avatar Honfika

Basic HTTP/2 support. Disabled by default. Warning added to the enable flag:

- only enabled when both client and server supports it (no protocol changing in proxy)
- GetRequest/ResponseBody(AsString) methods are not supported
- cannot modify the request/response (e.g header modifications in BeforeRequest/Response events are ignored)
parent a8a822a4
...@@ -150,6 +150,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -150,6 +150,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf
SessionListItem item = null; SessionListItem item = null;
await Dispatcher.InvokeAsync(() => { item = addSession(e); }); await Dispatcher.InvokeAsync(() => { item = addSession(e); });
if (e.HttpClient.ConnectRequest?.TunnelType == TunnelType.Http2)
{
// GetRequestBody for HTTP/2 currently not supported
return;
}
if (e.HttpClient.Request.HasBody) if (e.HttpClient.Request.HasBody)
{ {
e.HttpClient.Request.KeepBody = true; e.HttpClient.Request.KeepBody = true;
...@@ -168,6 +174,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -168,6 +174,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf
} }
}); });
if (e.HttpClient.ConnectRequest?.TunnelType == TunnelType.Http2)
{
// GetRequestBody for HTTP/2 currently not supported
return;
}
if (item != null) if (item != null)
{ {
if (e.HttpClient.Response.HasBody) if (e.HttpClient.Response.HasBody)
...@@ -217,6 +229,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -217,6 +229,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf
var session = (SessionEventArgsBase)sender; var session = (SessionEventArgsBase)sender;
if (sessionDictionary.TryGetValue(session.HttpClient, out var li)) if (sessionDictionary.TryGetValue(session.HttpClient, out var li))
{ {
var tunnelType = session.HttpClient.ConnectRequest?.TunnelType ?? TunnelType.Unknown;
if (tunnelType != TunnelType.Unknown)
{
li.Protocol = TunnelTypeToString(tunnelType);
}
li.ReceivedDataCount += args.Count; li.ReceivedDataCount += args.Count;
} }
}; };
...@@ -226,6 +244,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -226,6 +244,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf
var session = (SessionEventArgsBase)sender; var session = (SessionEventArgsBase)sender;
if (sessionDictionary.TryGetValue(session.HttpClient, out var li)) if (sessionDictionary.TryGetValue(session.HttpClient, out var li))
{ {
var tunnelType = session.HttpClient.ConnectRequest?.TunnelType ?? TunnelType.Unknown;
if (tunnelType != TunnelType.Unknown)
{
li.Protocol = TunnelTypeToString(tunnelType);
}
li.SentDataCount += args.Count; li.SentDataCount += args.Count;
} }
}; };
...@@ -235,6 +259,21 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -235,6 +259,21 @@ namespace Titanium.Web.Proxy.Examples.Wpf
return item; return item;
} }
private string TunnelTypeToString(TunnelType tunnelType)
{
switch (tunnelType)
{
case TunnelType.Https:
return "https";
case TunnelType.Websocket:
return "websocket";
case TunnelType.Http2:
return "http2";
}
return null;
}
private void ListViewSessions_OnKeyDown(object sender, KeyEventArgs e) private void ListViewSessions_OnKeyDown(object sender, KeyEventArgs e)
{ {
if (e.Key == Key.Delete) if (e.Key == Key.Delete)
......
...@@ -11,7 +11,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -11,7 +11,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
private long? bodySize; private long? bodySize;
private Exception exception; private Exception exception;
private string host; private string host;
private string process; private int processId;
private string protocol; private string protocol;
private long receivedDataCount; private long receivedDataCount;
private long sentDataCount; private long sentDataCount;
...@@ -54,10 +54,32 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -54,10 +54,32 @@ namespace Titanium.Web.Proxy.Examples.Wpf
set => SetField(ref bodySize, value); set => SetField(ref bodySize, value);
} }
public int ProcessId
{
get => processId;
set
{
if (SetField(ref processId, value))
{
OnPropertyChanged(nameof(Process));
}
}
}
public string Process public string Process
{ {
get => process; get
set => SetField(ref process, value); {
try
{
var process = System.Diagnostics.Process.GetProcessById(processId);
return process.ProcessName + ":" + processId;
}
catch (Exception)
{
return string.Empty;
}
}
} }
public long ReceivedDataCount public long ReceivedDataCount
...@@ -80,13 +102,16 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -80,13 +102,16 @@ namespace Titanium.Web.Proxy.Examples.Wpf
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
protected void SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null) protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{ {
if (!Equals(field, value)) if (!Equals(field, value))
{ {
field = value; field = value;
OnPropertyChanged(propertyName); OnPropertyChanged(propertyName);
return true;
} }
return false;
} }
[NotifyPropertyChangedInvocator] [NotifyPropertyChangedInvocator]
...@@ -132,20 +157,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -132,20 +157,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
BodySize = responseSize; BodySize = responseSize;
} }
Process = GetProcessDescription(HttpClient.ProcessId.Value); ProcessId = HttpClient.ProcessId.Value;
}
private string GetProcessDescription(int processId)
{
try
{
var process = System.Diagnostics.Process.GetProcessById(processId);
return process.ProcessName + ":" + processId;
}
catch (Exception)
{
return string.Empty;
}
} }
} }
} }
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj" />
</ItemGroup>
</Project>
\ No newline at end of file
...@@ -126,6 +126,7 @@ namespace Titanium.Web.Proxy ...@@ -126,6 +126,7 @@ namespace Titanium.Web.Proxy
bool isClientHello = clientHelloInfo != null; bool isClientHello = clientHelloInfo != null;
if (isClientHello) if (isClientHello)
{ {
connectRequest.TunnelType = TunnelType.Https;
connectRequest.ClientHelloInfo = clientHelloInfo; connectRequest.ClientHelloInfo = clientHelloInfo;
} }
...@@ -208,9 +209,9 @@ namespace Titanium.Web.Proxy ...@@ -208,9 +209,9 @@ namespace Titanium.Web.Proxy
} }
catch (Exception e) catch (Exception e)
{ {
var certname = certificate?.GetNameInfo(X509NameType.SimpleName, false); var certName = certificate?.GetNameInfo(X509NameType.SimpleName, false);
throw new ProxyConnectException( throw new ProxyConnectException(
$"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) == -1) if (await HttpHelper.IsConnectMethod(clientStream) == -1)
...@@ -233,6 +234,11 @@ namespace Titanium.Web.Proxy ...@@ -233,6 +234,11 @@ namespace Titanium.Web.Proxy
// Hostname is excluded or it is not an HTTPS connect // Hostname is excluded or it is not an HTTPS connect
if (!decryptSsl || !isClientHello) if (!decryptSsl || !isClientHello)
{ {
if (!isClientHello)
{
connectRequest.TunnelType = TunnelType.Websocket;
}
// 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.
...@@ -286,6 +292,8 @@ namespace Titanium.Web.Proxy ...@@ -286,6 +292,8 @@ namespace Titanium.Web.Proxy
string httpCmd = await clientStream.ReadLineAsync(cancellationToken); string httpCmd = await clientStream.ReadLineAsync(cancellationToken);
if (httpCmd == "PRI * HTTP/2.0") if (httpCmd == "PRI * HTTP/2.0")
{ {
connectArgs.HttpClient.ConnectRequest.TunnelType = TunnelType.Http2;
// HTTP/2 Connection Preface // HTTP/2 Connection Preface
string line = await clientStream.ReadLineAsync(cancellationToken); string line = await clientStream.ReadLineAsync(cancellationToken);
if (line != string.Empty) if (line != string.Empty)
...@@ -318,9 +326,16 @@ namespace Titanium.Web.Proxy ...@@ -318,9 +326,16 @@ namespace Titanium.Web.Proxy
await Http2Helper.SendHttp2(clientStream, connection.Stream, BufferSize, await Http2Helper.SendHttp2(clientStream, connection.Stream, BufferSize,
(buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); },
(buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); },
() => new SessionEventArgs(this, endPoint, cancellationTokenSource)
{
ProxyClient = { Connection = clientConnection },
HttpClient = { ConnectRequest = connectArgs?.HttpClient.ConnectRequest },
UserData = connectArgs?.UserData
},
async args => { await invokeBeforeRequest(args); },
async args => { await invokeBeforeResponse(args); },
connectArgs.CancellationTokenSource, clientConnection.Id, ExceptionFunc); connectArgs.CancellationTokenSource, clientConnection.Id, ExceptionFunc);
#endif #endif
} }
finally finally
{ {
......
...@@ -146,8 +146,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -146,8 +146,8 @@ namespace Titanium.Web.Proxy.Helpers
private static async Task<int> startsWith(ICustomStreamReader clientStreamReader, string expectedStart) private static async Task<int> startsWith(ICustomStreamReader clientStreamReader, string expectedStart)
{ {
bool isExpected = true; bool isExpected = true;
int legthToCheck = 10; int lengthToCheck = 10;
for (int i = 0; i < legthToCheck; i++) for (int i = 0; i < lengthToCheck; i++)
{ {
int b = await clientStreamReader.PeekByteAsync(i); int b = await clientStreamReader.PeekByteAsync(i);
if (b == -1) if (b == -1)
......
...@@ -12,6 +12,8 @@ namespace Titanium.Web.Proxy.Http ...@@ -12,6 +12,8 @@ namespace Titanium.Web.Proxy.Http
Method = "CONNECT"; Method = "CONNECT";
} }
public TunnelType TunnelType { get; internal set; }
public ClientHelloInfo ClientHelloInfo { get; set; } public ClientHelloInfo ClientHelloInfo { get; set; }
} }
} }
...@@ -6,28 +6,28 @@ ...@@ -6,28 +6,28 @@
public static class KnownHeaders public static class KnownHeaders
{ {
// Both // Both
public const string Connection = "connection"; public const string Connection = "Connection";
public const string ConnectionClose = "close"; public const string ConnectionClose = "close";
public const string ConnectionKeepAlive = "keep-alive"; public const string ConnectionKeepAlive = "keep-alive";
public const string ContentLength = "content-length"; public const string ContentLength = "Content-Length";
public const string ContentType = "content-type"; public const string ContentType = "Content-Type";
public const string ContentTypeCharset = "charset"; public const string ContentTypeCharset = "charset";
public const string ContentTypeBoundary = "boundary"; public const string ContentTypeBoundary = "boundary";
public const string Upgrade = "upgrade"; public const string Upgrade = "Upgrade";
public const string UpgradeWebsocket = "websocket"; public const string UpgradeWebsocket = "websocket";
// Request headers // Request headers
public const string AcceptEncoding = "accept-encoding"; public const string AcceptEncoding = "Accept-Encoding";
public const string Authorization = "Authorization"; public const string Authorization = "Authorization";
public const string Expect = "expect"; public const string Expect = "Expect";
public const string Expect100Continue = "100-continue"; public const string Expect100Continue = "100-continue";
public const string Host = "host"; public const string Host = "Host";
public const string ProxyAuthorization = "Proxy-Authorization"; public const string ProxyAuthorization = "Proxy-Authorization";
public const string ProxyAuthorizationBasic = "basic"; public const string ProxyAuthorizationBasic = "basic";
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
public const string ProxyConnectionClose = "close"; public const string ProxyConnectionClose = "close";
// Response headers // Response headers
public const string ContentEncoding = "content-encoding"; public const string ContentEncoding = "Content-Encoding";
public const string ContentEncodingDeflate = "deflate"; public const string ContentEncodingDeflate = "deflate";
public const string ContentEncodingGzip = "gzip"; public const string ContentEncodingGzip = "gzip";
public const string ContentEncodingBrotli = "br"; public const string ContentEncodingBrotli = "br";
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
public const string ProxyAuthenticate = "Proxy-Authenticate"; public const string ProxyAuthenticate = "Proxy-Authenticate";
public const string TransferEncoding = "transfer-encoding"; public const string TransferEncoding = "Transfer-Encoding";
public const string TransferEncodingChunked = "chunked"; public const string TransferEncodingChunked = "chunked";
} }
} }
namespace Titanium.Web.Proxy.Http
{
public enum TunnelType
{
Unknown,
Https,
Websocket,
Http2,
}
}
...@@ -59,99 +59,99 @@ namespace Titanium.Web.Proxy.Http2.Hpack ...@@ -59,99 +59,99 @@ namespace Titanium.Web.Proxy.Http2.Hpack
/* 14 */ /* 14 */
new HttpHeader(":status", "500"), new HttpHeader(":status", "500"),
/* 15 */ /* 15 */
new HttpHeader("accept-charset", string.Empty), new HttpHeader("Accept-Charset", string.Empty),
/* 16 */ /* 16 */
new HttpHeader("accept-encoding", "gzip, deflate"), new HttpHeader("Accept-Encoding", "gzip, deflate"),
/* 17 */ /* 17 */
new HttpHeader("accept-language", string.Empty), new HttpHeader("Accept-Language", string.Empty),
/* 18 */ /* 18 */
new HttpHeader("accept-ranges", string.Empty), new HttpHeader("Accept-Ranges", string.Empty),
/* 19 */ /* 19 */
new HttpHeader("accept", string.Empty), new HttpHeader("Accept", string.Empty),
/* 20 */ /* 20 */
new HttpHeader("access-control-allow-origin", string.Empty), new HttpHeader("Access-Control-Allow-Origin", string.Empty),
/* 21 */ /* 21 */
new HttpHeader("age", string.Empty), new HttpHeader("Age", string.Empty),
/* 22 */ /* 22 */
new HttpHeader("allow", string.Empty), new HttpHeader("Allow", string.Empty),
/* 23 */ /* 23 */
new HttpHeader("authorization", string.Empty), new HttpHeader("Authorization", string.Empty),
/* 24 */ /* 24 */
new HttpHeader("cache-control", string.Empty), new HttpHeader("Cache-Control", string.Empty),
/* 25 */ /* 25 */
new HttpHeader("content-disposition", string.Empty), new HttpHeader("Content-Disposition", string.Empty),
/* 26 */ /* 26 */
new HttpHeader("content-encoding", string.Empty), new HttpHeader("Content-Encoding", string.Empty),
/* 27 */ /* 27 */
new HttpHeader("content-language", string.Empty), new HttpHeader("Content-Language", string.Empty),
/* 28 */ /* 28 */
new HttpHeader("content-length", string.Empty), new HttpHeader("Content-Length", string.Empty),
/* 29 */ /* 29 */
new HttpHeader("content-location", string.Empty), new HttpHeader("Content-Location", string.Empty),
/* 30 */ /* 30 */
new HttpHeader("content-range", string.Empty), new HttpHeader("Content-Range", string.Empty),
/* 31 */ /* 31 */
new HttpHeader("content-type", string.Empty), new HttpHeader("Content-Type", string.Empty),
/* 32 */ /* 32 */
new HttpHeader("cookie", string.Empty), new HttpHeader("Cookie", string.Empty),
/* 33 */ /* 33 */
new HttpHeader("date", string.Empty), new HttpHeader("Date", string.Empty),
/* 34 */ /* 34 */
new HttpHeader("etag", string.Empty), new HttpHeader("ETag", string.Empty),
/* 35 */ /* 35 */
new HttpHeader("expect", string.Empty), new HttpHeader("Expect", string.Empty),
/* 36 */ /* 36 */
new HttpHeader("expires", string.Empty), new HttpHeader("Expires", string.Empty),
/* 37 */ /* 37 */
new HttpHeader("from", string.Empty), new HttpHeader("From", string.Empty),
/* 38 */ /* 38 */
new HttpHeader("host", string.Empty), new HttpHeader("Host", string.Empty),
/* 39 */ /* 39 */
new HttpHeader("if-match", string.Empty), new HttpHeader("If-Match", string.Empty),
/* 40 */ /* 40 */
new HttpHeader("if-modified-since", string.Empty), new HttpHeader("If-Modified-Since", string.Empty),
/* 41 */ /* 41 */
new HttpHeader("if-none-match", string.Empty), new HttpHeader("If-None-Match", string.Empty),
/* 42 */ /* 42 */
new HttpHeader("if-range", string.Empty), new HttpHeader("If-Range", string.Empty),
/* 43 */ /* 43 */
new HttpHeader("if-unmodified-since", string.Empty), new HttpHeader("If-Unmodified-Since", string.Empty),
/* 44 */ /* 44 */
new HttpHeader("last-modified", string.Empty), new HttpHeader("Last-Modified", string.Empty),
/* 45 */ /* 45 */
new HttpHeader("link", string.Empty), new HttpHeader("Link", string.Empty),
/* 46 */ /* 46 */
new HttpHeader("location", string.Empty), new HttpHeader("Location", string.Empty),
/* 47 */ /* 47 */
new HttpHeader("max-forwards", string.Empty), new HttpHeader("Max-Forwards", string.Empty),
/* 48 */ /* 48 */
new HttpHeader("proxy-authenticate", string.Empty), new HttpHeader("Proxy-Authenticate", string.Empty),
/* 49 */ /* 49 */
new HttpHeader("proxy-authorization", string.Empty), new HttpHeader("Proxy-Authorization", string.Empty),
/* 50 */ /* 50 */
new HttpHeader("range", string.Empty), new HttpHeader("Range", string.Empty),
/* 51 */ /* 51 */
new HttpHeader("referer", string.Empty), new HttpHeader("Referer", string.Empty),
/* 52 */ /* 52 */
new HttpHeader("refresh", string.Empty), new HttpHeader("Refresh", string.Empty),
/* 53 */ /* 53 */
new HttpHeader("retry-after", string.Empty), new HttpHeader("Retry-After", string.Empty),
/* 54 */ /* 54 */
new HttpHeader("server", string.Empty), new HttpHeader("Server", string.Empty),
/* 55 */ /* 55 */
new HttpHeader("set-cookie", string.Empty), new HttpHeader("Set-Cookie", string.Empty),
/* 56 */ /* 56 */
new HttpHeader("strict-transport-security", string.Empty), new HttpHeader("Strict-Transport-Security", string.Empty),
/* 57 */ /* 57 */
new HttpHeader("transfer-encoding", string.Empty), new HttpHeader("Transfer-Encoding", string.Empty),
/* 58 */ /* 58 */
new HttpHeader("user-agent", string.Empty), new HttpHeader("User-Agent", string.Empty),
/* 59 */ /* 59 */
new HttpHeader("vary", string.Empty), new HttpHeader("Vary", string.Empty),
/* 60 */ /* 60 */
new HttpHeader("via", string.Empty), new HttpHeader("Via", string.Empty),
/* 61 */ /* 61 */
new HttpHeader("www-authenticate", string.Empty) new HttpHeader("WWW-Authenticate", string.Empty)
}; };
private static readonly Dictionary<string, int> staticIndexByName = CreateMap(); private static readonly Dictionary<string, int> staticIndexByName = CreateMap();
...@@ -244,4 +244,4 @@ namespace Titanium.Web.Proxy.Http2.Hpack ...@@ -244,4 +244,4 @@ namespace Titanium.Web.Proxy.Http2.Hpack
return ret; return ret;
} }
} }
} }
\ No newline at end of file
This diff is collapsed.
...@@ -146,10 +146,13 @@ namespace Titanium.Web.Proxy ...@@ -146,10 +146,13 @@ namespace Titanium.Web.Proxy
public bool EnableWinAuth { get; set; } public bool EnableWinAuth { get; set; }
/// <summary> /// <summary>
/// Enable disable HTTP/2 support. This setting is internal, /// Enable disable HTTP/2 support.
/// because the implementation is not finished /// Warning: HTTP/2 support is very limited
/// - only enabled when both client and server supports it (no protocol changing in proxy)
/// - GetRequest/ResponseBody(AsString) methods are not supported
/// - cannot modify the request/response (e.g header modifications in BeforeRequest/Response events are ignored)
/// </summary> /// </summary>
internal bool EnableHttp2 { get; set; } = false; public bool EnableHttp2 { get; set; } = false;
/// <summary> /// <summary>
/// Should we check for certificate revocation during SSL authentication to servers /// Should we check for certificate revocation during SSL authentication to servers
......
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