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
SessionListItem item = null;
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)
{
e.HttpClient.Request.KeepBody = true;
......@@ -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 (e.HttpClient.Response.HasBody)
......@@ -217,6 +229,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf
var session = (SessionEventArgsBase)sender;
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;
}
};
......@@ -226,6 +244,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf
var session = (SessionEventArgsBase)sender;
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;
}
};
......@@ -235,6 +259,21 @@ namespace Titanium.Web.Proxy.Examples.Wpf
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)
{
if (e.Key == Key.Delete)
......
......@@ -11,7 +11,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
private long? bodySize;
private Exception exception;
private string host;
private string process;
private int processId;
private string protocol;
private long receivedDataCount;
private long sentDataCount;
......@@ -54,10 +54,32 @@ namespace Titanium.Web.Proxy.Examples.Wpf
set => SetField(ref bodySize, value);
}
public int ProcessId
{
get => processId;
set
{
if (SetField(ref processId, value))
{
OnPropertyChanged(nameof(Process));
}
}
}
public string Process
{
get => process;
set => SetField(ref process, value);
get
{
try
{
var process = System.Diagnostics.Process.GetProcessById(processId);
return process.ProcessName + ":" + processId;
}
catch (Exception)
{
return string.Empty;
}
}
}
public long ReceivedDataCount
......@@ -80,13 +102,16 @@ namespace Titanium.Web.Proxy.Examples.Wpf
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))
{
field = value;
OnPropertyChanged(propertyName);
return true;
}
return false;
}
[NotifyPropertyChangedInvocator]
......@@ -132,20 +157,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
BodySize = responseSize;
}
Process = GetProcessDescription(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;
}
ProcessId = HttpClient.ProcessId.Value;
}
}
}
<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
bool isClientHello = clientHelloInfo != null;
if (isClientHello)
{
connectRequest.TunnelType = TunnelType.Https;
connectRequest.ClientHelloInfo = clientHelloInfo;
}
......@@ -208,9 +209,9 @@ namespace Titanium.Web.Proxy
}
catch (Exception e)
{
var certname = certificate?.GetNameInfo(X509NameType.SimpleName, false);
var certName = certificate?.GetNameInfo(X509NameType.SimpleName, false);
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)
......@@ -233,6 +234,11 @@ namespace Titanium.Web.Proxy
// Hostname is excluded or it is not an HTTPS connect
if (!decryptSsl || !isClientHello)
{
if (!isClientHello)
{
connectRequest.TunnelType = TunnelType.Websocket;
}
// create new connection to server.
// If we detected that client tunnel CONNECTs without SSL by checking for empty client hello then
// this connection should not be HTTPS.
......@@ -286,6 +292,8 @@ namespace Titanium.Web.Proxy
string httpCmd = await clientStream.ReadLineAsync(cancellationToken);
if (httpCmd == "PRI * HTTP/2.0")
{
connectArgs.HttpClient.ConnectRequest.TunnelType = TunnelType.Http2;
// HTTP/2 Connection Preface
string line = await clientStream.ReadLineAsync(cancellationToken);
if (line != string.Empty)
......@@ -318,9 +326,16 @@ namespace Titanium.Web.Proxy
await Http2Helper.SendHttp2(clientStream, connection.Stream, BufferSize,
(buffer, offset, count) => { connectArgs.OnDataSent(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);
#endif
}
finally
{
......
......@@ -146,8 +146,8 @@ namespace Titanium.Web.Proxy.Helpers
private static async Task<int> startsWith(ICustomStreamReader clientStreamReader, string expectedStart)
{
bool isExpected = true;
int legthToCheck = 10;
for (int i = 0; i < legthToCheck; i++)
int lengthToCheck = 10;
for (int i = 0; i < lengthToCheck; i++)
{
int b = await clientStreamReader.PeekByteAsync(i);
if (b == -1)
......
......@@ -12,6 +12,8 @@ namespace Titanium.Web.Proxy.Http
Method = "CONNECT";
}
public TunnelType TunnelType { get; internal set; }
public ClientHelloInfo ClientHelloInfo { get; set; }
}
}
......@@ -6,28 +6,28 @@
public static class KnownHeaders
{
// Both
public const string Connection = "connection";
public const string Connection = "Connection";
public const string ConnectionClose = "close";
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 ContentTypeBoundary = "boundary";
public const string Upgrade = "upgrade";
public const string Upgrade = "Upgrade";
public const string UpgradeWebsocket = "websocket";
// Request headers
public const string AcceptEncoding = "accept-encoding";
public const string AcceptEncoding = "Accept-Encoding";
public const string Authorization = "Authorization";
public const string Expect = "expect";
public const string Expect = "Expect";
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 ProxyAuthorizationBasic = "basic";
......@@ -36,7 +36,7 @@
public const string ProxyConnectionClose = "close";
// Response headers
public const string ContentEncoding = "content-encoding";
public const string ContentEncoding = "Content-Encoding";
public const string ContentEncodingDeflate = "deflate";
public const string ContentEncodingGzip = "gzip";
public const string ContentEncodingBrotli = "br";
......@@ -45,7 +45,7 @@
public const string ProxyAuthenticate = "Proxy-Authenticate";
public const string TransferEncoding = "transfer-encoding";
public const string TransferEncoding = "Transfer-Encoding";
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
/* 14 */
new HttpHeader(":status", "500"),
/* 15 */
new HttpHeader("accept-charset", string.Empty),
new HttpHeader("Accept-Charset", string.Empty),
/* 16 */
new HttpHeader("accept-encoding", "gzip, deflate"),
new HttpHeader("Accept-Encoding", "gzip, deflate"),
/* 17 */
new HttpHeader("accept-language", string.Empty),
new HttpHeader("Accept-Language", string.Empty),
/* 18 */
new HttpHeader("accept-ranges", string.Empty),
new HttpHeader("Accept-Ranges", string.Empty),
/* 19 */
new HttpHeader("accept", string.Empty),
new HttpHeader("Accept", string.Empty),
/* 20 */
new HttpHeader("access-control-allow-origin", string.Empty),
new HttpHeader("Access-Control-Allow-Origin", string.Empty),
/* 21 */
new HttpHeader("age", string.Empty),
new HttpHeader("Age", string.Empty),
/* 22 */
new HttpHeader("allow", string.Empty),
new HttpHeader("Allow", string.Empty),
/* 23 */
new HttpHeader("authorization", string.Empty),
new HttpHeader("Authorization", string.Empty),
/* 24 */
new HttpHeader("cache-control", string.Empty),
new HttpHeader("Cache-Control", string.Empty),
/* 25 */
new HttpHeader("content-disposition", string.Empty),
new HttpHeader("Content-Disposition", string.Empty),
/* 26 */
new HttpHeader("content-encoding", string.Empty),
new HttpHeader("Content-Encoding", string.Empty),
/* 27 */
new HttpHeader("content-language", string.Empty),
new HttpHeader("Content-Language", string.Empty),
/* 28 */
new HttpHeader("content-length", string.Empty),
new HttpHeader("Content-Length", string.Empty),
/* 29 */
new HttpHeader("content-location", string.Empty),
new HttpHeader("Content-Location", string.Empty),
/* 30 */
new HttpHeader("content-range", string.Empty),
new HttpHeader("Content-Range", string.Empty),
/* 31 */
new HttpHeader("content-type", string.Empty),
new HttpHeader("Content-Type", string.Empty),
/* 32 */
new HttpHeader("cookie", string.Empty),
new HttpHeader("Cookie", string.Empty),
/* 33 */
new HttpHeader("date", string.Empty),
new HttpHeader("Date", string.Empty),
/* 34 */
new HttpHeader("etag", string.Empty),
new HttpHeader("ETag", string.Empty),
/* 35 */
new HttpHeader("expect", string.Empty),
new HttpHeader("Expect", string.Empty),
/* 36 */
new HttpHeader("expires", string.Empty),
new HttpHeader("Expires", string.Empty),
/* 37 */
new HttpHeader("from", string.Empty),
new HttpHeader("From", string.Empty),
/* 38 */
new HttpHeader("host", string.Empty),
new HttpHeader("Host", string.Empty),
/* 39 */
new HttpHeader("if-match", string.Empty),
new HttpHeader("If-Match", string.Empty),
/* 40 */
new HttpHeader("if-modified-since", string.Empty),
new HttpHeader("If-Modified-Since", string.Empty),
/* 41 */
new HttpHeader("if-none-match", string.Empty),
new HttpHeader("If-None-Match", string.Empty),
/* 42 */
new HttpHeader("if-range", string.Empty),
new HttpHeader("If-Range", string.Empty),
/* 43 */
new HttpHeader("if-unmodified-since", string.Empty),
new HttpHeader("If-Unmodified-Since", string.Empty),
/* 44 */
new HttpHeader("last-modified", string.Empty),
new HttpHeader("Last-Modified", string.Empty),
/* 45 */
new HttpHeader("link", string.Empty),
new HttpHeader("Link", string.Empty),
/* 46 */
new HttpHeader("location", string.Empty),
new HttpHeader("Location", string.Empty),
/* 47 */
new HttpHeader("max-forwards", string.Empty),
new HttpHeader("Max-Forwards", string.Empty),
/* 48 */
new HttpHeader("proxy-authenticate", string.Empty),
new HttpHeader("Proxy-Authenticate", string.Empty),
/* 49 */
new HttpHeader("proxy-authorization", string.Empty),
new HttpHeader("Proxy-Authorization", string.Empty),
/* 50 */
new HttpHeader("range", string.Empty),
new HttpHeader("Range", string.Empty),
/* 51 */
new HttpHeader("referer", string.Empty),
new HttpHeader("Referer", string.Empty),
/* 52 */
new HttpHeader("refresh", string.Empty),
new HttpHeader("Refresh", string.Empty),
/* 53 */
new HttpHeader("retry-after", string.Empty),
new HttpHeader("Retry-After", string.Empty),
/* 54 */
new HttpHeader("server", string.Empty),
new HttpHeader("Server", string.Empty),
/* 55 */
new HttpHeader("set-cookie", string.Empty),
new HttpHeader("Set-Cookie", string.Empty),
/* 56 */
new HttpHeader("strict-transport-security", string.Empty),
new HttpHeader("Strict-Transport-Security", string.Empty),
/* 57 */
new HttpHeader("transfer-encoding", string.Empty),
new HttpHeader("Transfer-Encoding", string.Empty),
/* 58 */
new HttpHeader("user-agent", string.Empty),
new HttpHeader("User-Agent", string.Empty),
/* 59 */
new HttpHeader("vary", string.Empty),
new HttpHeader("Vary", string.Empty),
/* 60 */
new HttpHeader("via", string.Empty),
new HttpHeader("Via", string.Empty),
/* 61 */
new HttpHeader("www-authenticate", string.Empty)
new HttpHeader("WWW-Authenticate", string.Empty)
};
private static readonly Dictionary<string, int> staticIndexByName = CreateMap();
......
This diff is collapsed.
......@@ -146,10 +146,13 @@ namespace Titanium.Web.Proxy
public bool EnableWinAuth { get; set; }
/// <summary>
/// Enable disable HTTP/2 support. This setting is internal,
/// because the implementation is not finished
/// Enable disable HTTP/2 support.
/// 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>
internal bool EnableHttp2 { get; set; } = false;
public bool EnableHttp2 { get; set; } = false;
/// <summary>
/// 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