diff --git a/.build/build.ps1 b/.build/build.ps1 index 3c3874fd7..238d8de6d 100644 --- a/.build/build.ps1 +++ b/.build/build.ps1 @@ -29,7 +29,7 @@ if($Branch -eq "beta" ) { $Version = "$Version-beta" } $NuGet = Join-Path $RepoRoot ".nuget\nuget.exe" -$MSBuild = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\msbuild.exe" +$MSBuild = "${env:ProgramFiles}\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\msbuild.exe" $MSBuild -replace ' ', '` ' FormatTaskName (("-"*25) + "[{0}]" + ("-"*25)) diff --git a/README.md b/README.md index 0081ef80a..0b4ab7e95 100644 --- a/README.md +++ b/README.md @@ -51,15 +51,15 @@ The owner of this project, [justcoding121](https://github.com/justcoding121), is ### Development environment #### Windows -* Visual Studio Code as IDE for .NET Core -* Visual Studio 2019 as IDE for .NET Framework/.NET Core +* Visual Studio Code as IDE for .NET +* Visual Studio 2022 as IDE for .NET Framework/.NET #### Mac OS -* Visual Studio Code as IDE for .NET Core -* Visual Studio 2019 as IDE for Mono +* Visual Studio Code as IDE for .NET +* Visual Studio 2022 as IDE for Mono #### Linux -* Visual Studio Code as IDE for .NET Core +* Visual Studio Code as IDE for .NET * Mono develop as IDE for Mono ### Usage @@ -93,7 +93,7 @@ var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true) }; // Fired when a CONNECT request is received -explicitEndPoint.BeforeTunnelConnect += OnBeforeTunnelConnect; +explicitEndPoint.BeforeTunnelConnectRequest += OnBeforeTunnelConnectRequest; // An explicit endpoint is where the client knows about the existence of a proxy // So client sends request in a proxy friendly manner @@ -127,7 +127,7 @@ proxyServer.SetAsSystemHttpsProxy(explicitEndPoint); Console.Read(); // Unsubscribe & Quit -explicitEndPoint.BeforeTunnelConnect -= OnBeforeTunnelConnect; +explicitEndPoint.BeforeTunnelConnectRequest -= OnBeforeTunnelConnectRequest; proxyServer.BeforeRequest -= OnRequest; proxyServer.BeforeResponse -= OnResponse; proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation; @@ -158,7 +158,7 @@ public async Task OnRequest(object sender, SessionEventArgs e) Console.WriteLine(e.HttpClient.Request.Url); // read request headers - var requestHeaders = e.HttpClient.Request.RequestHeaders; + var requestHeaders = e.HttpClient.Request.Headers; var method = e.HttpClient.Request.Method.ToUpper(); if ((method == "POST" || method == "PUT" || method == "PATCH")) @@ -200,12 +200,12 @@ public async Task OnRequest(object sender, SessionEventArgs e) public async Task OnResponse(object sender, SessionEventArgs e) { // read response headers - var responseHeaders = e.HttpClient.Response.ResponseHeaders; + var responseHeaders = e.HttpClient.Response.Headers; //if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return; if (e.HttpClient.Request.Method == "GET" || e.HttpClient.Request.Method == "POST") { - if (e.HttpClient.Response.ResponseStatusCode == "200") + if (e.HttpClient.Response.StatusCode == 200) { if (e.HttpClient.Response.ContentType != null && e.HttpClient.Response.ContentType.Trim().ToLower().Contains("text/html")) { diff --git a/appveyor.yml b/appveyor.yml index 71cbcb40a..b90f85ec1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,7 +8,7 @@ # version format version: 3.1.{build} -image: Visual Studio 2019 +image: Visual Studio 2022 shallow_clone: false diff --git a/examples/Titanium.Web.Proxy.Examples.Basic/Properties/AssemblyInfo.cs b/examples/Titanium.Web.Proxy.Examples.Basic/Properties/AssemblyInfo.cs deleted file mode 100644 index 8490a716c..000000000 --- a/examples/Titanium.Web.Proxy.Examples.Basic/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Demo")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Demo")] -[assembly: AssemblyCopyright("Copyright © Titanium 2015-2019")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("9a2c6980-90d1-4082-ad60-b2428f3d6197")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/examples/Titanium.Web.Proxy.Examples.Basic/Titanium.Web.Proxy.Examples.Basic.Mono.csproj b/examples/Titanium.Web.Proxy.Examples.Basic/Titanium.Web.Proxy.Examples.Basic.Mono.csproj deleted file mode 100644 index 80a278309..000000000 --- a/examples/Titanium.Web.Proxy.Examples.Basic/Titanium.Web.Proxy.Examples.Basic.Mono.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - Exe - net461 - false - 7.1 - AnyCPU;x64 - - - - - - - \ No newline at end of file diff --git a/examples/Titanium.Web.Proxy.Examples.Basic/Titanium.Web.Proxy.Examples.Basic.NetCore.csproj b/examples/Titanium.Web.Proxy.Examples.Basic/Titanium.Web.Proxy.Examples.Basic.NetCore.csproj deleted file mode 100644 index 7c33920f3..000000000 --- a/examples/Titanium.Web.Proxy.Examples.Basic/Titanium.Web.Proxy.Examples.Basic.NetCore.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - Exe - net50 - false - 7.1 - - - - - - - - - - - \ No newline at end of file diff --git a/examples/Titanium.Web.Proxy.Examples.Basic/Titanium.Web.Proxy.Examples.Basic.csproj b/examples/Titanium.Web.Proxy.Examples.Basic/Titanium.Web.Proxy.Examples.Basic.csproj index b92fd5a6e..a76dca8cd 100644 --- a/examples/Titanium.Web.Proxy.Examples.Basic/Titanium.Web.Proxy.Examples.Basic.csproj +++ b/examples/Titanium.Web.Proxy.Examples.Basic/Titanium.Web.Proxy.Examples.Basic.csproj @@ -2,9 +2,7 @@ Exe - net461;net50 - false - 7.1 + net48;net60 diff --git a/examples/Titanium.Web.Proxy.Examples.WindowsService/App.config b/examples/Titanium.Web.Proxy.Examples.WindowsService/App.config index ae789a6c9..7be895aa1 100644 --- a/examples/Titanium.Web.Proxy.Examples.WindowsService/App.config +++ b/examples/Titanium.Web.Proxy.Examples.WindowsService/App.config @@ -1,26 +1,26 @@  - -
+ +
- + - - + + - - + + - - + + diff --git a/examples/Titanium.Web.Proxy.Examples.WindowsService/Properties/Settings.Designer.cs b/examples/Titanium.Web.Proxy.Examples.WindowsService/Properties/Settings.Designer.cs index 681c91c1d..ab7241999 100644 --- a/examples/Titanium.Web.Proxy.Examples.WindowsService/Properties/Settings.Designer.cs +++ b/examples/Titanium.Web.Proxy.Examples.WindowsService/Properties/Settings.Designer.cs @@ -13,7 +13,7 @@ namespace WindowsServiceExample.Properties [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.3.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.0.3.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { diff --git a/examples/Titanium.Web.Proxy.Examples.WindowsService/Titanium.Web.Proxy.Examples.WindowsService.csproj b/examples/Titanium.Web.Proxy.Examples.WindowsService/Titanium.Web.Proxy.Examples.WindowsService.csproj index 0e9afb2a4..c24478490 100644 --- a/examples/Titanium.Web.Proxy.Examples.WindowsService/Titanium.Web.Proxy.Examples.WindowsService.csproj +++ b/examples/Titanium.Web.Proxy.Examples.WindowsService/Titanium.Web.Proxy.Examples.WindowsService.csproj @@ -8,7 +8,7 @@ win WindowsServiceExample WindowsServiceExample - v4.7.2 + v4.8 512 true true @@ -29,6 +29,7 @@ 1.0.0.%2a false true + AnyCPU @@ -121,5 +122,8 @@ + + + \ No newline at end of file diff --git a/examples/Titanium.Web.Proxy.Examples.Wpf/Properties/AssemblyInfo.cs b/examples/Titanium.Web.Proxy.Examples.Wpf/Properties/AssemblyInfo.cs deleted file mode 100644 index cca79748f..000000000 --- a/examples/Titanium.Web.Proxy.Examples.Wpf/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; -using System.Windows; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Demo WPF")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Demo WPF")] -[assembly: AssemblyCopyright("Copyright © Titanium 2015-2019")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -//In order to begin building localizable applications, set -//CultureYouAreCodingWith in your .csproj file -//inside a . For example, if you are using US english -//in your source files, set the to en-US. Then uncomment -//the NeutralResourceLanguage attribute below. Update the "en-US" in -//the line below to match the UICulture setting in the project file. - -//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] - - -[assembly: ThemeInfo( - ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located - //(used if a resource is not found in the page, - // or application resource dictionaries) - ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located - //(used if a resource is not found in the page, - // app, or any theme specific resource dictionaries) -)] - - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/examples/Titanium.Web.Proxy.Examples.Wpf/Titanium.Web.Proxy.Examples.Wpf.NetCore.csproj b/examples/Titanium.Web.Proxy.Examples.Wpf/Titanium.Web.Proxy.Examples.Wpf.NetCore.csproj deleted file mode 100644 index fb17f5421..000000000 --- a/examples/Titanium.Web.Proxy.Examples.Wpf/Titanium.Web.Proxy.Examples.Wpf.NetCore.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - WinExe - net50 - true - - - - - - - - - - - \ No newline at end of file diff --git a/examples/Titanium.Web.Proxy.Examples.Wpf/Titanium.Web.Proxy.Examples.Wpf.csproj b/examples/Titanium.Web.Proxy.Examples.Wpf/Titanium.Web.Proxy.Examples.Wpf.csproj index caa5c1810..b8dcee626 100644 --- a/examples/Titanium.Web.Proxy.Examples.Wpf/Titanium.Web.Proxy.Examples.Wpf.csproj +++ b/examples/Titanium.Web.Proxy.Examples.Wpf/Titanium.Web.Proxy.Examples.Wpf.csproj @@ -1,17 +1,11 @@ - + WinExe - net461;net50-windows + net48;net60-windows true - - - - - - diff --git a/src/Titanium.Web.Proxy/Certificates/Cache/DefaultCertificateDiskCache.cs b/src/Titanium.Web.Proxy/Certificates/Cache/DefaultCertificateDiskCache.cs index 910df634e..6f4740179 100644 --- a/src/Titanium.Web.Proxy/Certificates/Cache/DefaultCertificateDiskCache.cs +++ b/src/Titanium.Web.Proxy/Certificates/Cache/DefaultCertificateDiskCache.cs @@ -129,6 +129,14 @@ private string getRootCertificateDirectory() assemblyLocation = Assembly.GetEntryAssembly().Location; } +#if NETSTANDARD2_1 + // single-file app returns string.Empty location + if (assemblyLocation == string.Empty) + { + assemblyLocation = AppContext.BaseDirectory; + } +#endif + string path = Path.GetDirectoryName(assemblyLocation); rootCertificatePath = path ?? throw new NullReferenceException(); diff --git a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs index 4e6630b10..a36a36503 100644 --- a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs +++ b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs @@ -85,6 +85,11 @@ private async Task readRequestBodyAsync(CancellationToken cancellationToken) // If not already read (not cached yet) if (!request.IsBodyRead) { + if (request.IsBodyReceived) + { + throw new Exception("Request body was already received."); + } + if (request.HttpVersion == HttpHeader.Version20) { // do not send to the remote endpoint @@ -103,6 +108,7 @@ private async Task readRequestBodyAsync(CancellationToken cancellationToken) // Now set the flag to true // So that next time we can deliver body from cache request.IsBodyRead = true; + request.IsBodyReceived = true; } else { @@ -115,6 +121,7 @@ private async Task readRequestBodyAsync(CancellationToken cancellationToken) // Now set the flag to true // So that next time we can deliver body from cache request.IsBodyRead = true; + request.IsBodyReceived = true; } } } @@ -160,6 +167,11 @@ private async Task readResponseBodyAsync(CancellationToken cancellationToken) // If not already read (not cached yet) if (!response.IsBodyRead) { + if (response.IsBodyReceived) + { + throw new Exception("Response body was already received."); + } + if (response.HttpVersion == HttpHeader.Version20) { // do not send to the remote endpoint @@ -178,6 +190,7 @@ private async Task readResponseBodyAsync(CancellationToken cancellationToken) // Now set the flag to true // So that next time we can deliver body from cache response.IsBodyRead = true; + response.IsBodyReceived = true; } else { @@ -190,6 +203,7 @@ private async Task readResponseBodyAsync(CancellationToken cancellationToken) // Now set the flag to true // So that next time we can deliver body from cache response.IsBodyRead = true; + response.IsBodyReceived = true; } } } @@ -221,7 +235,7 @@ private async Task readBodyAsync(bool isRequest, CancellationToken cance internal async Task SyphonOutBodyAsync(bool isRequest, CancellationToken cancellationToken) { var requestResponse = isRequest ? (RequestResponseBase)HttpClient.Request : HttpClient.Response; - if (requestResponse.IsBodyRead || !requestResponse.OriginalHasBody) + if (requestResponse.IsBodyReceived || !requestResponse.OriginalHasBody) { return; } @@ -229,6 +243,7 @@ internal async Task SyphonOutBodyAsync(bool isRequest, CancellationToken cancell var reader = isRequest ? (HttpStream)ClientStream : HttpClient.Connection.Stream; await reader.CopyBodyAsync(requestResponse, true, NullWriter.Instance, TransformationMode.None, isRequest, this, cancellationToken); + requestResponse.IsBodyReceived = true; } /// @@ -272,11 +287,15 @@ internal async Task CopyRequestBodyAsync(IHttpStreamWriter writer, Transformatio { await reader.CopyBodyAsync(request, false, writer, transformation, true, this, cancellationToken); } + + request.IsBodyReceived = true; } private async Task copyResponseBodyAsync(IHttpStreamWriter writer, TransformationMode transformation, CancellationToken cancellationToken) { - await HttpClient.Connection.Stream.CopyBodyAsync(HttpClient.Response, false, writer, transformation, false, this, cancellationToken); + var response = HttpClient.Response; + await HttpClient.Connection.Stream.CopyBodyAsync(response, false, writer, transformation, false, this, cancellationToken); + response.IsBodyReceived = true; } /// diff --git a/src/Titanium.Web.Proxy/Http/RequestResponseBase.cs b/src/Titanium.Web.Proxy/Http/RequestResponseBase.cs index 825cc8db8..8fb1b6eaa 100644 --- a/src/Titanium.Web.Proxy/Http/RequestResponseBase.cs +++ b/src/Titanium.Web.Proxy/Http/RequestResponseBase.cs @@ -211,6 +211,8 @@ internal set internal bool BodyAvailable => BodyInternal != null; + internal bool IsBodyReceived { get; set; } + internal bool IsBodySent { get; set; } internal abstract void EnsureBodyAvailable(bool throwWhenNotReadYet = true); diff --git a/src/Titanium.Web.Proxy/Http2/Http2Helper.cs b/src/Titanium.Web.Proxy/Http2/Http2Helper.cs index d84341874..125e5185a 100644 --- a/src/Titanium.Web.Proxy/Http2/Http2Helper.cs +++ b/src/Titanium.Web.Proxy/Http2/Http2Helper.cs @@ -394,6 +394,7 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, } rr.IsBodyRead = true; + rr.IsBodyReceived = true; var tcs = rr.ReadHttp2BodyTaskCompletionSource; rr.ReadHttp2BodyTaskCompletionSource = null; diff --git a/src/Titanium.Web.Proxy/Network/TcpConnection/TcpConnectionFactory.cs b/src/Titanium.Web.Proxy/Network/TcpConnection/TcpConnectionFactory.cs index 3194682fc..23e86a54e 100644 --- a/src/Titanium.Web.Proxy/Network/TcpConnection/TcpConnectionFactory.cs +++ b/src/Titanium.Web.Proxy/Network/TcpConnection/TcpConnectionFactory.cs @@ -538,6 +538,8 @@ internal Task GetServerConnection(ProxyServer proxyServer, connectRequest.Headers.AddHeader(HttpHeader.GetProxyAuthorizationHeader(externalProxy.UserName, externalProxy.Password)); } + await proxyServer.onBeforeUpStreamConnectRequest(connectRequest); + await stream.WriteRequestAsync(connectRequest, cancellationToken); var httpStatus = await stream.ReadResponseStatus(cancellationToken); diff --git a/src/Titanium.Web.Proxy/ProxyServer.cs b/src/Titanium.Web.Proxy/ProxyServer.cs index bf4f625bf..ca0220af6 100644 --- a/src/Titanium.Web.Proxy/ProxyServer.cs +++ b/src/Titanium.Web.Proxy/ProxyServer.cs @@ -11,6 +11,7 @@ using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers.WinHttp; +using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network.Tcp; @@ -387,6 +388,11 @@ public ExceptionHandler ExceptionFunc /// public event AsyncEventHandler? OnServerConnectionCreate; + /// + /// Intercept connect request sent to upstream proxy. + /// + public event AsyncEventHandler? BeforeUpStreamConnectRequest; + /// /// Customize the minimum ThreadPool size (increase it on a server) /// diff --git a/src/Titanium.Web.Proxy/RequestHandler.cs b/src/Titanium.Web.Proxy/RequestHandler.cs index 005c2daa7..ba6b502d8 100644 --- a/src/Titanium.Web.Proxy/RequestHandler.cs +++ b/src/Titanium.Web.Proxy/RequestHandler.cs @@ -406,6 +406,19 @@ private async Task onBeforeRequest(SessionEventArgs args) } } + /// + /// Invoke before request handler if it is set. + /// + /// The COONECT request. + /// + internal async Task onBeforeUpStreamConnectRequest(ConnectRequest request) + { + if (BeforeUpStreamConnectRequest != null) + { + await BeforeUpStreamConnectRequest.InvokeAsync(this, request, ExceptionFunc); + } + } + #if DEBUG internal bool ShouldCallBeforeRequestBodyWrite() { diff --git a/src/Titanium.Web.Proxy/ResponseHandler.cs b/src/Titanium.Web.Proxy/ResponseHandler.cs index 0d07c9d3b..b294244ba 100644 --- a/src/Titanium.Web.Proxy/ResponseHandler.cs +++ b/src/Titanium.Web.Proxy/ResponseHandler.cs @@ -120,6 +120,8 @@ await handleHttpSessionRequest(args, null, args.ClientConnection.NegotiatedAppli await serverStream.CopyBodyAsync(response, false, clientStream, TransformationMode.None, false, args, cancellationToken); } + + response.IsBodyReceived = true; } args.TimeLine["Response Sent"] = DateTime.UtcNow; diff --git a/tests/Titanium.Web.Proxy.IntegrationTests/Titanium.Web.Proxy.IntegrationTests.csproj b/tests/Titanium.Web.Proxy.IntegrationTests/Titanium.Web.Proxy.IntegrationTests.csproj index 0b45452c3..79af3429b 100644 --- a/tests/Titanium.Web.Proxy.IntegrationTests/Titanium.Web.Proxy.IntegrationTests.csproj +++ b/tests/Titanium.Web.Proxy.IntegrationTests/Titanium.Web.Proxy.IntegrationTests.csproj @@ -1,7 +1,7 @@  - net50 + net60 StrongNameKey.snk true