├── .github ├── docfx.json ├── install-unit-test-certificate.ps1 ├── lib │ └── rootCert.pfx └── workflows │ └── dotnetcore.yml ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── LICENSE ├── NuGet.config ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── _config.yml ├── docs ├── api │ ├── Titanium.Web.Proxy.EventArguments.AsyncEventHandler-1.html │ ├── Titanium.Web.Proxy.EventArguments.BeforeSslAuthenticateEventArgs.html │ ├── Titanium.Web.Proxy.EventArguments.CertificateSelectionEventArgs.html │ ├── Titanium.Web.Proxy.EventArguments.CertificateValidationEventArgs.html │ ├── Titanium.Web.Proxy.EventArguments.DataEventArgs.html │ ├── Titanium.Web.Proxy.EventArguments.EmptyProxyEventArgs.html │ ├── Titanium.Web.Proxy.EventArguments.MultipartRequestPartSentEventArgs.html │ ├── Titanium.Web.Proxy.EventArguments.ProxyEventArgsBase.html │ ├── Titanium.Web.Proxy.EventArguments.SessionEventArgs.html │ ├── Titanium.Web.Proxy.EventArguments.SessionEventArgsBase.html │ ├── Titanium.Web.Proxy.EventArguments.TunnelConnectSessionEventArgs.html │ ├── Titanium.Web.Proxy.EventArguments.html │ ├── Titanium.Web.Proxy.ExceptionHandler.html │ ├── Titanium.Web.Proxy.Exceptions.BodyNotFoundException.html │ ├── Titanium.Web.Proxy.Exceptions.ProxyAuthorizationException.html │ ├── Titanium.Web.Proxy.Exceptions.ProxyConnectException.html │ ├── Titanium.Web.Proxy.Exceptions.ProxyException.html │ ├── Titanium.Web.Proxy.Exceptions.ProxyHttpException.html │ ├── Titanium.Web.Proxy.Exceptions.RetryableServerConnectionException.html │ ├── Titanium.Web.Proxy.Exceptions.ServerConnectionException.html │ ├── Titanium.Web.Proxy.Exceptions.html │ ├── Titanium.Web.Proxy.Extensions.SslApplicationProtocol.html │ ├── Titanium.Web.Proxy.Extensions.SslClientAuthenticationOptions.html │ ├── Titanium.Web.Proxy.Extensions.SslServerAuthenticationOptions.html │ ├── Titanium.Web.Proxy.Extensions.html │ ├── Titanium.Web.Proxy.Helpers.ProxyProtocolType.html │ ├── Titanium.Web.Proxy.Helpers.Ref-1.html │ ├── Titanium.Web.Proxy.Helpers.RunTime.html │ ├── Titanium.Web.Proxy.Helpers.html │ ├── Titanium.Web.Proxy.Http.ConnectRequest.html │ ├── Titanium.Web.Proxy.Http.ConnectResponse.html │ ├── Titanium.Web.Proxy.Http.HeaderCollection.html │ ├── Titanium.Web.Proxy.Http.HttpWebClient.html │ ├── Titanium.Web.Proxy.Http.KnownHeader.html │ ├── Titanium.Web.Proxy.Http.KnownHeaders.html │ ├── Titanium.Web.Proxy.Http.Request.html │ ├── Titanium.Web.Proxy.Http.RequestResponseBase.html │ ├── Titanium.Web.Proxy.Http.Response.html │ ├── Titanium.Web.Proxy.Http.Responses.GenericResponse.html │ ├── Titanium.Web.Proxy.Http.Responses.OkResponse.html │ ├── Titanium.Web.Proxy.Http.Responses.RedirectResponse.html │ ├── Titanium.Web.Proxy.Http.Responses.html │ ├── Titanium.Web.Proxy.Http.TunnelType.html │ ├── Titanium.Web.Proxy.Http.html │ ├── Titanium.Web.Proxy.Http2.Hpack.Decoder.html │ ├── Titanium.Web.Proxy.Http2.Hpack.DynamicTable.html │ ├── Titanium.Web.Proxy.Http2.Hpack.Encoder.html │ ├── Titanium.Web.Proxy.Http2.Hpack.HpackUtil.IndexType.html │ ├── Titanium.Web.Proxy.Http2.Hpack.HpackUtil.html │ ├── Titanium.Web.Proxy.Http2.Hpack.HuffmanDecoder.html │ ├── Titanium.Web.Proxy.Http2.Hpack.HuffmanEncoder.html │ ├── Titanium.Web.Proxy.Http2.Hpack.IHeaderListener.html │ ├── Titanium.Web.Proxy.Http2.Hpack.StaticTable.html │ ├── Titanium.Web.Proxy.Http2.Hpack.html │ ├── Titanium.Web.Proxy.Models.ExplicitProxyEndPoint.html │ ├── Titanium.Web.Proxy.Models.ExternalProxy.html │ ├── Titanium.Web.Proxy.Models.ExternalProxyType.html │ ├── Titanium.Web.Proxy.Models.HttpHeader.html │ ├── Titanium.Web.Proxy.Models.IExternalProxy.html │ ├── Titanium.Web.Proxy.Models.ProxyAuthenticationContext.html │ ├── Titanium.Web.Proxy.Models.ProxyAuthenticationResult.html │ ├── Titanium.Web.Proxy.Models.ProxyEndPoint.html │ ├── Titanium.Web.Proxy.Models.ProxyProtocolType.html │ ├── Titanium.Web.Proxy.Models.SocksProxyEndPoint.html │ ├── Titanium.Web.Proxy.Models.TransparentBaseProxyEndPoint.html │ ├── Titanium.Web.Proxy.Models.TransparentProxyEndPoint.html │ ├── Titanium.Web.Proxy.Models.html │ ├── Titanium.Web.Proxy.Network.Certificate.AlternativeNameType.html │ ├── Titanium.Web.Proxy.Network.Certificate.EncodingType.html │ ├── Titanium.Web.Proxy.Network.Certificate.html │ ├── Titanium.Web.Proxy.Network.CertificateEngine.html │ ├── Titanium.Web.Proxy.Network.CertificateManager.html │ ├── Titanium.Web.Proxy.Network.DefaultCertificateDiskCache.html │ ├── Titanium.Web.Proxy.Network.ICertificateCache.html │ ├── Titanium.Web.Proxy.Network.WinAuth.WinAuthHandler.html │ ├── Titanium.Web.Proxy.Network.WinAuth.html │ ├── Titanium.Web.Proxy.Network.html │ ├── Titanium.Web.Proxy.ProxyServer.html │ ├── Titanium.Web.Proxy.StreamExtended.BufferPool.IBufferPool.html │ ├── Titanium.Web.Proxy.StreamExtended.BufferPool.html │ ├── Titanium.Web.Proxy.StreamExtended.ClientHelloInfo.html │ ├── Titanium.Web.Proxy.StreamExtended.Models.SslExtension.html │ ├── Titanium.Web.Proxy.StreamExtended.Models.html │ ├── Titanium.Web.Proxy.StreamExtended.Network.DataEventArgs.html │ ├── Titanium.Web.Proxy.StreamExtended.Network.IHttpStreamReader.html │ ├── Titanium.Web.Proxy.StreamExtended.Network.IHttpStreamWriter.html │ ├── Titanium.Web.Proxy.StreamExtended.Network.ILineStream.html │ ├── Titanium.Web.Proxy.StreamExtended.Network.IPeekStream.html │ ├── Titanium.Web.Proxy.StreamExtended.Network.TaskResult-1.html │ ├── Titanium.Web.Proxy.StreamExtended.Network.TaskResult.html │ ├── Titanium.Web.Proxy.StreamExtended.Network.html │ ├── Titanium.Web.Proxy.StreamExtended.ServerHelloInfo.html │ ├── Titanium.Web.Proxy.StreamExtended.html │ ├── Titanium.Web.Proxy.WebSocketDecoder.html │ ├── Titanium.Web.Proxy.WebSocketFrame.html │ ├── Titanium.Web.Proxy.WebsocketOpCode.html │ ├── Titanium.Web.Proxy.html │ └── toc.html ├── favicon.ico ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── index.json ├── logo.svg ├── search-stopwords.json ├── styles │ ├── docfx.css │ ├── docfx.js │ ├── docfx.vendor.css │ ├── docfx.vendor.js │ ├── lunr.js │ ├── lunr.min.js │ ├── main.css │ ├── main.js │ └── search-worker.js └── xrefmap.yml ├── examples ├── Titanium.Web.Proxy.Examples.Basic │ ├── App.config │ ├── Capture.PNG │ ├── Helpers │ │ └── ConsoleHelper.cs │ ├── Program.cs │ ├── ProxyEventArgsBaseExtensions.cs │ ├── ProxyTestController.cs │ ├── SampleClientState.cs │ └── Titanium.Web.Proxy.Examples.Basic.csproj ├── Titanium.Web.Proxy.Examples.WindowsService │ ├── App.config │ ├── Program.cs │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ ├── Settings.Designer.cs │ │ └── Settings.settings │ ├── ProxyService.Designer.cs │ ├── ProxyService.cs │ ├── ProxyService.resx │ ├── Titanium.Web.Proxy.Examples.WindowsService.csproj │ ├── install.ps1 │ └── remove.ps1 └── Titanium.Web.Proxy.Examples.Wpf │ ├── App.config │ ├── App.xaml │ ├── App.xaml.cs │ ├── Capture.PNG │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── ObservableCollectionEx.cs │ ├── Properties │ ├── Annotations.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings │ ├── SessionListItem.cs │ └── Titanium.Web.Proxy.Examples.Wpf.csproj ├── omnisharp.json ├── src ├── Titanium.Web.Proxy.Docs.sln ├── Titanium.Web.Proxy.sln └── Titanium.Web.Proxy │ ├── Certificates │ ├── Cache │ │ ├── CachedCertificate.cs │ │ ├── DefaultCertificateDiskCache.cs │ │ └── ICertificateCache.cs │ ├── CertificateManager.cs │ └── Makers │ │ ├── BCCertificateMaker.cs │ │ ├── BCCertificateMakerFast.cs │ │ ├── ICertificateMaker.cs │ │ └── WinCertificateMaker.cs │ ├── Compression │ ├── CompressionFactory.cs │ └── DecompressionFactory.cs │ ├── EventArguments │ ├── BeforeBodyWriteEventArgs.cs │ ├── BeforeSslAuthenticateEventArgs.cs │ ├── CertificateSelectionEventArgs.cs │ ├── CertificateValidationEventArgs.cs │ ├── DataEventArgs.cs │ ├── EmptyProxyEventArgs.cs │ ├── MultipartRequestPartSentEventArgs.cs │ ├── ProxyEventArgsBase.cs │ ├── SessionEventArgs.cs │ ├── SessionEventArgsBase.cs │ ├── TransformationMode.cs │ └── TunnelConnectEventArgs.cs │ ├── Exceptions │ ├── BodyNotFoundException.cs │ ├── ProxyAuthorizationException.cs │ ├── ProxyConnectException.cs │ ├── ProxyException.cs │ ├── ProxyHttpException.cs │ └── RetryableServerConnectionException.cs │ ├── ExplicitClientHandler.cs │ ├── Extensions │ ├── FuncExtensions.cs │ ├── HttpHeaderExtensions.cs │ ├── SslExtensions.cs │ ├── StreamExtensions.cs │ ├── StringExtensions.cs │ ├── TcpExtensions.cs │ └── UriExtensions.cs │ ├── Handlers │ ├── AsyncEventHandler.cs │ ├── CertificateHandler.cs │ ├── ExceptionHandler.cs │ ├── ProxyAuthorizationHandler.cs │ ├── WebSocketHandler.cs │ └── WinAuthHandler.cs │ ├── Helpers │ ├── CompressionUtil.cs │ ├── HttpHelper.cs │ ├── NativeMethods.SystemProxy.cs │ ├── NativeMethods.Tcp.cs │ ├── Net45Compatibility.cs │ ├── Network.cs │ ├── ProxyInfo.cs │ ├── RunTime.cs │ ├── SystemProxy.cs │ ├── TcpHelper.cs │ └── WinHttp │ │ ├── NativeMethods.WinHttp.cs │ │ ├── WinHttpHandle.cs │ │ └── WinHttpWebProxyFinder.cs │ ├── Http │ ├── ConnectRequest.cs │ ├── ConnectResponse.cs │ ├── HeaderBuilder.cs │ ├── HeaderCollection.cs │ ├── HeaderParser.cs │ ├── InternalDataStore.cs │ ├── KnownHeader.cs │ ├── KnownHeaders.cs │ ├── Request.cs │ ├── RequestResponseBase.cs │ ├── Response.cs │ ├── Responses │ │ ├── GenericResponse.cs │ │ ├── OkResponse.cs │ │ └── RedirectResponse.cs │ └── TunnelType.cs │ ├── Http2 │ ├── Hpack │ │ ├── Decoder.cs │ │ ├── DynamicTable.cs │ │ ├── Encoder.cs │ │ ├── HpackUtil.cs │ │ ├── HuffmanDecoder.cs │ │ ├── HuffmanEncoder.cs │ │ ├── IHeaderListener.cs │ │ └── StaticTable.cs │ ├── Http2FrameFlag.cs │ ├── Http2FrameHeader.cs │ ├── Http2FrameType.cs │ └── Http2Helper.cs │ ├── Models │ ├── ByteString.cs │ ├── ExplicitProxyEndPoint.cs │ ├── ExternalProxy.cs │ ├── HttpCompression.cs │ ├── HttpHeader.cs │ ├── IExternalProxy.cs │ ├── KnownMethod.cs │ ├── ProxyAuthenticationContext.cs │ ├── ProxyEndPoint.cs │ ├── ProxyProtocolType.cs │ ├── RequestStatusInfo.cs │ ├── ResponseStatusInfo.cs │ ├── SocksProxyEndPoint.cs │ ├── TransparentBaseProxyEndPoint.cs │ └── TransparentProxyEndPoint.cs │ ├── Network │ ├── BufferPool │ │ ├── DefaultBufferPool.cs │ │ └── IBufferPool.cs │ ├── HttpWebClient.cs │ ├── Models │ │ ├── SslCiphers.cs │ │ ├── SslExtension.cs │ │ └── TaskResult.cs │ ├── Readers │ │ ├── IHttpStreamReader.cs │ │ └── PeekStreamReader.cs │ ├── RetryPolicy.cs │ ├── Ssl │ │ ├── ClientHelloInfo.cs │ │ ├── ServerHelloInfo.cs │ │ └── SslTools.cs │ ├── Streams │ │ ├── CopyStream.cs │ │ ├── HttpClientStream.cs │ │ ├── HttpServerStream.cs │ │ ├── HttpStream.cs │ │ ├── ILineStream.cs │ │ ├── IPeekStream.cs │ │ └── LimitedStream.cs │ ├── TcpConnection │ │ ├── TcpClientConnection.cs │ │ ├── TcpConnectionFactory.cs │ │ └── TcpServerConnection.cs │ ├── WinAuth │ │ ├── Security │ │ │ ├── Common.cs │ │ │ ├── LittleEndian.cs │ │ │ ├── Message.cs │ │ │ ├── State.cs │ │ │ └── WinAuthEndPoint.cs │ │ └── WinAuthHandler.cs │ └── Writers │ │ ├── IHttpStreamWriter.cs │ │ └── NullWriter.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── ProxyServer.cs │ ├── ProxySocket │ ├── Authentication │ │ ├── AuthMethod.cs │ │ ├── AuthNone.cs │ │ └── AuthUserPass.cs │ ├── HttpsHandler.cs │ ├── IAsyncProxyResult.cs │ ├── ProxyException.cs │ ├── ProxySocket.cs │ ├── Socks4Handler.cs │ ├── Socks5Handler.cs │ └── SocksHandler.cs │ ├── RequestHandler.cs │ ├── ResponseHandler.cs │ ├── Shared │ └── ProxyConstants.cs │ ├── SocksClientHandler.cs │ ├── StrongNameKey.snk │ ├── Titanium.Web.Proxy.csproj │ ├── TransparentClientHandler.cs │ ├── WebSocket │ ├── WebSocketDecoder.cs │ ├── WebSocketFrame.cs │ └── WebsocketOpCode.cs │ └── app.config └── tests ├── Titanium.Web.Proxy.IntegrationTests ├── .editorconfig ├── ExpectContinueTests.cs ├── Helpers │ ├── HttpContinueClient.cs │ ├── HttpContinueServer.cs │ ├── HttpMessageParsing.cs │ └── TestHelper.cs ├── HttpsTests.cs ├── InterceptionTests.cs ├── NestedProxyTests.cs ├── ReverseProxyTests.cs ├── Setup │ ├── TestProxyServer.cs │ ├── TestServer.cs │ └── TestSuite.cs ├── StressTests.cs ├── StrongNameKey.snk ├── Titanium.Web.Proxy.IntegrationTests.csproj └── Titanium.Web.Proxy.IntegrationTests.sln └── Titanium.Web.Proxy.UnitTests ├── CertificateManagerTests.cs ├── Properties └── AssemblyInfo.cs ├── ProxyServerTests.cs ├── StrongNameKey.snk ├── SystemProxyTest.cs ├── Titanium.Web.Proxy.UnitTests.csproj └── WinAuthTests.cs /.github/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [ 3 | { 4 | "src": [ 5 | { 6 | "files": ["Titanium.Web.Proxy.Docs.sln"], 7 | "src": "../src/" 8 | } 9 | ], 10 | "dest": "obj/api" 11 | } 12 | ], 13 | "build": { 14 | "content": [ 15 | { 16 | "files": ["**/*.yml"], 17 | "src": "obj/api", 18 | "dest": "api" 19 | }, 20 | { 21 | "files": ["*.md"] 22 | } 23 | ], 24 | "resource": [ 25 | { 26 | "files": [""] 27 | } 28 | ], 29 | "overwrite": "specs/*.md", 30 | "globalMetadata": { 31 | "_appTitle": "Titanium Web Proxy", 32 | "_enableSearch": true 33 | }, 34 | "dest": "../docs", 35 | "xrefService": ["https://xref.docs.microsoft.com/query?uid={uid}"] 36 | } 37 | } -------------------------------------------------------------------------------- /.github/install-unit-test-certificate.ps1: -------------------------------------------------------------------------------- 1 | $Here = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" 2 | $certPath = "$Here\lib\rootCert.pfx" 3 | $pfx = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2($certPath,"","Exportable,PersistKeySet") 4 | $store = new-object System.Security.Cryptography.X509Certificates.X509Store([System.Security.Cryptography.X509Certificates.StoreName]::Root, "localmachine") 5 | $store.open("MaxAllowed") 6 | $store.add($pfx) 7 | $store.close() -------------------------------------------------------------------------------- /.github/lib/rootCert.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/titanium-web-proxy/902504a324425e4e49fc5ba604c2b7fa172e68ce/.github/lib/rootCert.pfx -------------------------------------------------------------------------------- /.github/workflows/dotnetcore.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: 4 | push: 5 | branches: 6 | - develop 7 | - beta 8 | - stable 9 | pull_request: 10 | branches: 11 | - develop 12 | - beta 13 | - stable 14 | 15 | jobs: 16 | build: 17 | runs-on: windows-latest 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - name: Install DocFX 22 | if: github.ref == 'refs/heads/develop' 23 | run: choco install docfx -y 24 | 25 | - name: Build 26 | run: dotnet build src/Titanium.Web.Proxy/Titanium.Web.Proxy.csproj 27 | 28 | - name: Test 29 | shell: pwsh 30 | run: | 31 | .\.github\install-unit-test-certificate.ps1 32 | dotnet test tests/Titanium.Web.Proxy.UnitTests/Titanium.Web.Proxy.UnitTests.csproj 33 | dotnet test tests/Titanium.Web.Proxy.IntegrationTests/Titanium.Web.Proxy.IntegrationTests.csproj 34 | 35 | - name: Update Documentation 36 | if: github.ref == 'refs/heads/develop' 37 | run: docfx .github/docfx.json 38 | 39 | - name: Publish Documentation 40 | if: github.ref == 'refs/heads/develop' 41 | uses: EndBug/add-and-commit@v9 42 | with: 43 | default_author: github_actions 44 | message: Update documentation 45 | committer_name: GitHub Actions 46 | committer_email: actions@github.com 47 | 48 | - name: Publish Beta 49 | if: github.ref == 'refs/heads/beta' 50 | run: | 51 | dotnet pack src/Titanium.Web.Proxy/Titanium.Web.Proxy.csproj --version-suffix "beta" 52 | dotnet nuget push **\*.nupkg -s "nuget" -k "${{ secrets.NUGET_TOKEN }}" 53 | 54 | - name: Publish Stable 55 | if: github.ref == 'refs/heads/stable' 56 | run: | 57 | dotnet pack src/Titanium.Web.Proxy/Titanium.Web.Proxy.csproj 58 | dotnet nuget push **\*.nupkg -s "nuget" -k "${{ secrets.NUGET_TOKEN }}" 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | # DNX 42 | project.lock.json 43 | artifacts/ 44 | 45 | *_i.c 46 | *_p.c 47 | *_i.h 48 | *.ilk 49 | *.meta 50 | *.obj 51 | *.pch 52 | *.pdb 53 | *.pgc 54 | *.pgd 55 | *.rsp 56 | *.sbr 57 | *.tlb 58 | *.tli 59 | *.tlh 60 | *.tmp 61 | *.tmp_proj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | 70 | # Chutzpah Test files 71 | _Chutzpah* 72 | 73 | # Visual C++ cache files 74 | ipch/ 75 | *.aps 76 | *.ncb 77 | *.opensdf 78 | *.sdf 79 | *.cachefile 80 | 81 | # Visual Studio profiler 82 | *.psess 83 | *.vsp 84 | *.vspx 85 | 86 | # TFS 2012 Local Workspace 87 | $tf/ 88 | 89 | # Guidance Automation Toolkit 90 | *.gpState 91 | 92 | # ReSharper is a .NET coding add-in 93 | _ReSharper*/ 94 | *.[Rr]e[Ss]harper 95 | *.DotSettings.user 96 | 97 | # JustCode is a .NET coding add-in 98 | .JustCode 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | _NCrunch_* 108 | .*crunch*.local.xml 109 | 110 | # MightyMoose 111 | *.mm.* 112 | AutoTest.Net/ 113 | 114 | # Web workbench (sass) 115 | .sass-cache/ 116 | 117 | # Installshield output folder 118 | [Ee]xpress/ 119 | 120 | # DocProject is a documentation generator add-in 121 | DocProject/buildhelp/ 122 | DocProject/Help/*.HxT 123 | DocProject/Help/*.HxC 124 | DocProject/Help/*.hhc 125 | DocProject/Help/*.hhk 126 | DocProject/Help/*.hhp 127 | DocProject/Help/Html2 128 | DocProject/Help/html 129 | 130 | # Click-Once directory 131 | publish/ 132 | 133 | # Publish Web Output 134 | *.[Pp]ublish.xml 135 | *.azurePubxml 136 | # TODO: Comment the next line if you want to checkin your web deploy settings 137 | # but database connection strings (with potential passwords) will be unencrypted 138 | *.pubxml 139 | *.publishproj 140 | 141 | # NuGet Packages 142 | *.nupkg 143 | # The packages folder can be ignored because of Package Restore 144 | **/packages/* 145 | # except build/, which is used as an MSBuild target. 146 | !**/packages/build/ 147 | # Uncomment if necessary however generally it will be regenerated when needed 148 | #!**/packages/repositories.config 149 | 150 | # Windows Azure Build Output 151 | csx/ 152 | *.build.csdef 153 | 154 | # Windows Store app package directory 155 | AppPackages/ 156 | 157 | # Visual Studio cache files 158 | # files ending in .cache can be ignored 159 | *.[Cc]ache 160 | # but keep track of directories ending in .cache 161 | !*.[Cc]ache/ 162 | 163 | # Others 164 | ClientBin/ 165 | [Ss]tyle[Cc]op.* 166 | ~$* 167 | *~ 168 | *.dbmdl 169 | *.dbproj.schemaview 170 | *.pfx 171 | *.publishsettings 172 | node_modules/ 173 | orleans.codegen.cs 174 | 175 | # RIA/Silverlight projects 176 | Generated_Code/ 177 | 178 | # Backup & report files from converting an old project file 179 | # to a newer Visual Studio version. Backup files are not needed, 180 | # because we have git ;-) 181 | _UpgradeReport_Files/ 182 | Backup*/ 183 | UpgradeLog*.XML 184 | UpgradeLog*.htm 185 | 186 | # SQL Server files 187 | *.mdf 188 | *.ldf 189 | 190 | # Business Intelligence projects 191 | *.rdl.data 192 | *.bim.layout 193 | *.bim_*.settings 194 | 195 | # Microsoft Fakes 196 | FakesAssemblies/ 197 | 198 | # Node.js Tools for Visual Studio 199 | .ntvs_analysis.dat 200 | 201 | # Visual Studio 6 build log 202 | *.plg 203 | 204 | # Visual Studio 6 workspace options file 205 | *.opt 206 | 207 | # Docfx 208 | docs/manifest.json 209 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "NetCore|Debug|Basic Example", 6 | "type": "coreclr", 7 | "request": "launch", 8 | "program": "${workspaceRoot}/examples/Titanium.Web.Proxy.Examples.Basic/bin/Debug/netcoreapp3.1/Titanium.Web.Proxy.Examples.Basic.NetCore.dll", 9 | "args": [], 10 | "cwd": "${workspaceRoot}", 11 | "stopAtEntry": false, 12 | "console": "integratedTerminal", 13 | "preLaunchTask": "build-basic-example-netcore-debug" 14 | }, 15 | { 16 | "name": "NetCore|Release|Basic Example", 17 | "type": "coreclr", 18 | "request": "launch", 19 | "program": "${workspaceRoot}/examples/Titanium.Web.Proxy.Examples.Basic/bin/Release/netcoreapp3.1/Titanium.Web.Proxy.Examples.Basic.NetCore.dll", 20 | "args": [], 21 | "cwd": "${workspaceRoot}", 22 | "stopAtEntry": false, 23 | "console": "integratedTerminal", 24 | "preLaunchTask": "build-basic-example-netcore-release" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // The following will hide the js and map files in the editor 3 | "files.exclude": { 4 | "**/.build": true, 5 | "**/.nuget": true, 6 | "**/.vs": true, 7 | "**/docs": true, 8 | "**/packages": true, 9 | "**/bin": true, 10 | "**/obj": true, 11 | "**/*.DotSettings": true, 12 | "**/*.sln": true, 13 | "**/tests/" : true, 14 | "**/Titanium.Web.Proxy.Examples.Wpf/" : true, 15 | "**/*.Basic.csproj/": true, 16 | "**/*.Docs.csproj/": true, 17 | "**/*.Proxy.csproj/": true, 18 | "**/*.Mono.csproj" : true 19 | }, 20 | "search.exclude": { 21 | "**/.build": true, 22 | "**/.nuget": true, 23 | "**/.vs": true, 24 | "**/docs": true, 25 | "**/packages": true, 26 | "**/bin": true, 27 | "**/obj": true, 28 | "**/*.DotSettings": true, 29 | "**/*.sln": true, 30 | "**/tests/" : true, 31 | "**/Titanium.Web.Proxy.Examples.Wpf/" : true, 32 | "**/*.Basic.csproj/": true, 33 | "**/*.Docs.csproj/": true, 34 | "**/*.Proxy.csproj/": true, 35 | "**/*.Mono.csproj" : true 36 | } 37 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build-basic-example-netcore-debug", 6 | "type": "process", 7 | "command": "dotnet", 8 | "args": ["build","${workspaceFolder}/examples/Titanium.Web.Proxy.Examples.Basic/Titanium.Web.Proxy.Examples.Basic.NetCore.csproj"], 9 | "problemMatcher": "$msCompile", 10 | "group": { 11 | "kind": "build", 12 | "isDefault": true 13 | } 14 | }, 15 | { 16 | "label": "build-basic-example-netcore-release", 17 | "type": "process", 18 | "command": "dotnet", 19 | "args": ["build","${workspaceFolder}/examples/Titanium.Web.Proxy.Examples.Basic/Titanium.Web.Proxy.Examples.Basic.NetCore.csproj", "-c", "Release"], 20 | "problemMatcher": "$msCompile" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 titanium007 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Doneness: 2 | - [ ] Build is okay - I made sure that this change is building successfully. 3 | - [ ] No Bugs - I made sure that this change is working properly as expected. It doesn't have any bugs that you are aware of. 4 | - [ ] Branching - If this is not a hotfix, I am making this request against the develop branch 5 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | baseurl: /Titanium-Web-Proxy 2 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/titanium-web-proxy/902504a324425e4e49fc5ba604c2b7fa172e68ce/docs/favicon.ico -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/titanium-web-proxy/902504a324425e4e49fc5ba604c2b7fa172e68ce/docs/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/titanium-web-proxy/902504a324425e4e49fc5ba604c2b7fa172e68ce/docs/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/titanium-web-proxy/902504a324425e4e49fc5ba604c2b7fa172e68ce/docs/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/titanium-web-proxy/902504a324425e4e49fc5ba604c2b7fa172e68ce/docs/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /docs/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by Docfx 9 | 10 | 12 | 15 | 21 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/search-stopwords.json: -------------------------------------------------------------------------------- 1 | [ 2 | "a", 3 | "able", 4 | "about", 5 | "across", 6 | "after", 7 | "all", 8 | "almost", 9 | "also", 10 | "am", 11 | "among", 12 | "an", 13 | "and", 14 | "any", 15 | "are", 16 | "as", 17 | "at", 18 | "be", 19 | "because", 20 | "been", 21 | "but", 22 | "by", 23 | "can", 24 | "cannot", 25 | "could", 26 | "dear", 27 | "did", 28 | "do", 29 | "does", 30 | "either", 31 | "else", 32 | "ever", 33 | "every", 34 | "for", 35 | "from", 36 | "get", 37 | "got", 38 | "had", 39 | "has", 40 | "have", 41 | "he", 42 | "her", 43 | "hers", 44 | "him", 45 | "his", 46 | "how", 47 | "however", 48 | "i", 49 | "if", 50 | "in", 51 | "into", 52 | "is", 53 | "it", 54 | "its", 55 | "just", 56 | "least", 57 | "let", 58 | "like", 59 | "likely", 60 | "may", 61 | "me", 62 | "might", 63 | "most", 64 | "must", 65 | "my", 66 | "neither", 67 | "no", 68 | "nor", 69 | "not", 70 | "of", 71 | "off", 72 | "often", 73 | "on", 74 | "only", 75 | "or", 76 | "other", 77 | "our", 78 | "own", 79 | "rather", 80 | "said", 81 | "say", 82 | "says", 83 | "she", 84 | "should", 85 | "since", 86 | "so", 87 | "some", 88 | "than", 89 | "that", 90 | "the", 91 | "their", 92 | "them", 93 | "then", 94 | "there", 95 | "these", 96 | "they", 97 | "this", 98 | "tis", 99 | "to", 100 | "too", 101 | "twas", 102 | "us", 103 | "wants", 104 | "was", 105 | "we", 106 | "were", 107 | "what", 108 | "when", 109 | "where", 110 | "which", 111 | "while", 112 | "who", 113 | "whom", 114 | "why", 115 | "will", 116 | "with", 117 | "would", 118 | "yet", 119 | "you", 120 | "your" 121 | ] 122 | -------------------------------------------------------------------------------- /docs/styles/main.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/titanium-web-proxy/902504a324425e4e49fc5ba604c2b7fa172e68ce/docs/styles/main.css -------------------------------------------------------------------------------- /docs/styles/main.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information. 2 | -------------------------------------------------------------------------------- /docs/styles/search-worker.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | importScripts('lunr.min.js'); 3 | 4 | var lunrIndex; 5 | 6 | var stopWords = null; 7 | var searchData = {}; 8 | 9 | lunr.tokenizer.separator = /[\s\-\.\(\)]+/; 10 | 11 | var stopWordsRequest = new XMLHttpRequest(); 12 | stopWordsRequest.open('GET', '../search-stopwords.json'); 13 | stopWordsRequest.onload = function () { 14 | if (this.status != 200) { 15 | return; 16 | } 17 | stopWords = JSON.parse(this.responseText); 18 | buildIndex(); 19 | } 20 | stopWordsRequest.send(); 21 | 22 | var searchDataRequest = new XMLHttpRequest(); 23 | 24 | searchDataRequest.open('GET', '../index.json'); 25 | searchDataRequest.onload = function () { 26 | if (this.status != 200) { 27 | return; 28 | } 29 | searchData = JSON.parse(this.responseText); 30 | 31 | buildIndex(); 32 | 33 | postMessage({ e: 'index-ready' }); 34 | } 35 | searchDataRequest.send(); 36 | 37 | onmessage = function (oEvent) { 38 | var q = oEvent.data.q; 39 | var hits = lunrIndex.search(q); 40 | var results = []; 41 | hits.forEach(function (hit) { 42 | var item = searchData[hit.ref]; 43 | results.push({ 'href': item.href, 'title': item.title, 'keywords': item.keywords }); 44 | }); 45 | postMessage({ e: 'query-ready', q: q, d: results }); 46 | } 47 | 48 | function buildIndex() { 49 | if (stopWords !== null && !isEmpty(searchData)) { 50 | lunrIndex = lunr(function () { 51 | this.pipeline.remove(lunr.stopWordFilter); 52 | this.ref('href'); 53 | this.field('title', { boost: 50 }); 54 | this.field('keywords', { boost: 20 }); 55 | 56 | for (var prop in searchData) { 57 | if (searchData.hasOwnProperty(prop)) { 58 | this.add(searchData[prop]); 59 | } 60 | } 61 | 62 | var docfxStopWordFilter = lunr.generateStopWordFilter(stopWords); 63 | lunr.Pipeline.registerFunction(docfxStopWordFilter, 'docfxStopWordFilter'); 64 | this.pipeline.add(docfxStopWordFilter); 65 | this.searchPipeline.add(docfxStopWordFilter); 66 | }); 67 | } 68 | } 69 | 70 | function isEmpty(obj) { 71 | if(!obj) return true; 72 | 73 | for (var prop in obj) { 74 | if (obj.hasOwnProperty(prop)) 75 | return false; 76 | } 77 | 78 | return true; 79 | } 80 | })(); 81 | -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.Basic/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.Basic/Capture.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/titanium-web-proxy/902504a324425e4e49fc5ba604c2b7fa172e68ce/examples/Titanium.Web.Proxy.Examples.Basic/Capture.PNG -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.Basic/Helpers/ConsoleHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Titanium.Web.Proxy.Examples.Basic.Helpers 5 | { 6 | /// 7 | /// Adapted from 8 | /// http://stackoverflow.com/questions/13656846/how-to-programmatic-disable-c-sharp-console-applications-quick-edit-mode 9 | /// 10 | internal static class ConsoleHelper 11 | { 12 | private const uint EnableQuickEdit = 0x0040; 13 | 14 | // STD_INPUT_HANDLE (DWORD): -10 is the standard input device. 15 | private const int StdInputHandle = -10; 16 | 17 | [DllImport("kernel32.dll", SetLastError = true)] 18 | private static extern IntPtr GetStdHandle(int nStdHandle); 19 | 20 | [DllImport("kernel32.dll")] 21 | private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode); 22 | 23 | [DllImport("kernel32.dll")] 24 | private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode); 25 | 26 | internal static bool DisableQuickEditMode() 27 | { 28 | var consoleHandle = GetStdHandle(StdInputHandle); 29 | 30 | // get current console mode 31 | if (!GetConsoleMode(consoleHandle, out var consoleMode)) 32 | // ERROR: Unable to get console mode. 33 | return false; 34 | 35 | // Clear the quick edit bit in the mode flags 36 | consoleMode &= ~EnableQuickEdit; 37 | 38 | // set the new mode 39 | if (!SetConsoleMode(consoleHandle, consoleMode)) 40 | // ERROR: Unable to set console mode 41 | return false; 42 | 43 | return true; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.Basic/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Titanium.Web.Proxy.Examples.Basic.Helpers; 3 | using Titanium.Web.Proxy.Helpers; 4 | 5 | namespace Titanium.Web.Proxy.Examples.Basic 6 | { 7 | public class Program 8 | { 9 | private static readonly ProxyTestController controller = new ProxyTestController(); 10 | 11 | public static void Main(string[] args) 12 | { 13 | if (RunTime.IsWindows) 14 | // fix console hang due to QuickEdit mode 15 | ConsoleHelper.DisableQuickEditMode(); 16 | 17 | // Start proxy controller 18 | controller.StartProxy(); 19 | 20 | Console.WriteLine("Hit any key to exit.."); 21 | Console.WriteLine(); 22 | Console.Read(); 23 | 24 | controller.Stop(); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.Basic/ProxyEventArgsBaseExtensions.cs: -------------------------------------------------------------------------------- 1 | using Titanium.Web.Proxy.EventArguments; 2 | 3 | namespace Titanium.Web.Proxy.Examples.Basic 4 | { 5 | public static class ProxyEventArgsBaseExtensions 6 | { 7 | public static SampleClientState GetState(this ProxyEventArgsBase args) 8 | { 9 | if (args.ClientUserData == null) args.ClientUserData = new SampleClientState(); 10 | 11 | return (SampleClientState)args.ClientUserData; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.Basic/SampleClientState.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace Titanium.Web.Proxy.Examples.Basic 4 | { 5 | public class SampleClientState 6 | { 7 | public StringBuilder PipelineInfo { get; } = new StringBuilder(); 8 | } 9 | } -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.Basic/Titanium.Web.Proxy.Examples.Basic.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net48;net60 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.WindowsService/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 7 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 8080 35 | 36 | 37 | True 38 | 39 | 40 | NoCheck 41 | 42 | 43 | 30 44 | 45 | 46 | False 47 | 48 | 49 | True 50 | 51 | 52 | True 53 | 54 | 55 | False 56 | 57 | 58 | False 59 | 60 | 61 | 2 62 | 63 | 64 | True 65 | 66 | 67 | 30 68 | 69 | 70 | True 71 | 72 | 73 | False 74 | 75 | 76 | True 77 | 78 | 79 | -1 80 | 81 | 82 | False 83 | 84 | 85 | True 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.WindowsService/Program.cs: -------------------------------------------------------------------------------- 1 | using System.ServiceProcess; 2 | 3 | namespace WindowsServiceExample 4 | { 5 | internal static class Program 6 | { 7 | /// 8 | /// The main entry point for the application. 9 | /// 10 | private static void Main() 11 | { 12 | ServiceBase[] servicesToRun; 13 | servicesToRun = new ServiceBase[] 14 | { 15 | new ProxyService() 16 | }; 17 | ServiceBase.Run(servicesToRun); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.WindowsService/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("WindowsServiceExample")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("WindowsServiceExample")] 12 | [assembly: AssemblyCopyright("Copyright © 2019")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("63635f50-7d6f-4a93-84c6-04cf9d2754c5")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.WindowsService/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 8080 7 | 8 | 9 | True 10 | 11 | 12 | NoCheck 13 | 14 | 15 | 30 16 | 17 | 18 | False 19 | 20 | 21 | True 22 | 23 | 24 | True 25 | 26 | 27 | False 28 | 29 | 30 | False 31 | 32 | 33 | 2 34 | 35 | 36 | True 37 | 38 | 39 | 30 40 | 41 | 42 | True 43 | 44 | 45 | False 46 | 47 | 48 | True 49 | 50 | 51 | -1 52 | 53 | 54 | False 55 | 56 | 57 | True 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.WindowsService/ProxyService.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace WindowsServiceExample 2 | { 3 | partial class ProxyService 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.ProxyServiceEventLog = new System.Diagnostics.EventLog(); 32 | ((System.ComponentModel.ISupportInitialize)(this.ProxyServiceEventLog)).BeginInit(); 33 | // 34 | // ProxyServiceEventLog 35 | // 36 | this.ProxyServiceEventLog.Log = "Application"; 37 | this.ProxyServiceEventLog.Source = "ProxyService"; 38 | // 39 | // ProxyService 40 | // 41 | this.ServiceName = "ProxyService"; 42 | ((System.ComponentModel.ISupportInitialize)(this.ProxyServiceEventLog)).EndInit(); 43 | 44 | } 45 | 46 | #endregion 47 | private System.Diagnostics.EventLog ProxyServiceEventLog; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.WindowsService/install.ps1: -------------------------------------------------------------------------------- 1 | # 2 | # There are several ways to install a service on windows, this methods uses PowerShell. 3 | # 4 | 5 | # Self-elevate the script if required. 6 | if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) { 7 | if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) { 8 | $CommandLine = "-File `"" + $MyInvocation.MyCommand.Path + "`" " + $MyInvocation.UnboundArguments 9 | Start-Process -FilePath PowerShell.exe -Verb Runas -ArgumentList $CommandLine 10 | Exit 11 | } 12 | } 13 | 14 | # This is the name of the service and will also show as the display name in services.msc. 15 | [String] $ServiceName = "ProxyService" 16 | # This is the name of the executable of the service. 17 | [String] $ServiceExeName = "WindowsServiceExample.exe" 18 | # Use the directory of the runnign script and the service executable name to create a full path. 19 | [String] $ServiceExePath = [string]($PSScriptRoot) + "\" + $ServiceExeName 20 | # Get the information for the executable file. 21 | [IO.FileInfo] $ExeFileInfo = $ServiceExePath 22 | 23 | # Check if the executable file exists. 24 | if(!$ExeFileInfo.Exists) { 25 | # OH NO the executable was not found. 26 | Write-host "Service executable not found $ServiceExePath" 27 | Write-Host "Please fix and try again." 28 | 29 | }else{ 30 | # Lets install the service. 31 | Write-host "Installing service $ServiceExePath" 32 | New-Service -Name $ServiceName -BinaryPathName $ServiceExePath -Description "HTTP proxy service" -StartupType "Automatic" 33 | # Service installed, lets start it. 34 | Start-Service -Name $ServiceName 35 | } 36 | 37 | Read-Host -Prompt "Press Enter to exit" -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.WindowsService/remove.ps1: -------------------------------------------------------------------------------- 1 | # Self-elevate the script if required. 2 | if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) { 3 | if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) { 4 | $CommandLine = "-File `"" + $MyInvocation.MyCommand.Path + "`" " + $MyInvocation.UnboundArguments 5 | Start-Process -FilePath PowerShell.exe -Verb Runas -ArgumentList $CommandLine 6 | Exit 7 | } 8 | } 9 | 10 | # This is the name of the service and will also show as the display name in services.msc. 11 | # This must match what the service was installed as. 12 | $ServiceName = "ProxyService" 13 | 14 | # Make sure the service is stopped. 15 | Stop-Service -Name $ServiceName 16 | # Remove the service, this doesnt always work. (Requires PS 6+) 17 | Remove-Service -Name $ServiceName 18 | # Make sure the service gets unregistered even if the last command failed. 19 | sc.exe delete $ServiceName 20 | 21 | Read-Host -Prompt "Press Enter to exit" -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.Wpf/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.Wpf/App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.Wpf/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace Titanium.Web.Proxy.Examples.Wpf 4 | { 5 | /// 6 | /// Interaction logic for App.xaml 7 | /// 8 | public partial class App : Application 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.Wpf/Capture.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/titanium-web-proxy/902504a324425e4e49fc5ba604c2b7fa172e68ce/examples/Titanium.Web.Proxy.Examples.Wpf/Capture.PNG -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.Wpf/ObservableCollectionEx.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.Collections.Specialized; 3 | 4 | namespace Titanium.Web.Proxy.Examples.Wpf 5 | { 6 | public class ObservableCollectionEx : ObservableCollection 7 | { 8 | private bool notificationSuppressed; 9 | private bool suppressNotification; 10 | 11 | public bool SuppressNotification 12 | { 13 | get => suppressNotification; 14 | set 15 | { 16 | suppressNotification = value; 17 | if (suppressNotification == false && notificationSuppressed) 18 | { 19 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 20 | notificationSuppressed = false; 21 | } 22 | } 23 | } 24 | 25 | protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) 26 | { 27 | if (SuppressNotification) 28 | { 29 | notificationSuppressed = true; 30 | return; 31 | } 32 | 33 | base.OnCollectionChanged(e); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.Wpf/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Titanium.Web.Proxy.Examples.Wpf.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Titanium.Web.Proxy.Examples.Wpf.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.Wpf/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Titanium.Web.Proxy.Examples.Wpf.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.1.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.Wpf/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/Titanium.Web.Proxy.Examples.Wpf/Titanium.Web.Proxy.Examples.Wpf.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net48;net60-windows 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | True 20 | True 21 | Resources.resx 22 | 23 | 24 | True 25 | Settings.settings 26 | True 27 | 28 | 29 | ResXFileCodeGenerator 30 | Resources.Designer.cs 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /omnisharp.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileOptions": { 3 | "excludeSearchPatterns": [ 4 | "**/*.sln", 5 | "**/*.Docs.csproj", 6 | "**/tests/", 7 | "**/Titanium.Web.Proxy.Examples.Wpf/", 8 | "**/*.Basic.csproj", 9 | "**/*.Proxy.csproj", 10 | "**/*.Mono.csproj" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy.Docs.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29102.190 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Titanium.Web.Proxy", "Titanium.Web.Proxy\Titanium.Web.Proxy.csproj", "{91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Release|Any CPU = Release|Any CPU 13 | Release|x64 = Release|x64 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Debug|x64.ActiveCfg = Debug|Any CPU 19 | {91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Debug|x64.Build.0 = Debug|Any CPU 20 | {91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Release|x64.ActiveCfg = Release|Any CPU 23 | {91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}.Release|x64.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | EnterpriseLibraryConfigurationToolBinariesPath = .1.505.2\lib\NET35 30 | SolutionGuid = {625C1EB5-44CF-47DE-A85A-B4C8C40ED90A} 31 | EndGlobalSection 32 | EndGlobal 33 | -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Certificates/Cache/CachedCertificate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography.X509Certificates; 3 | 4 | namespace Titanium.Web.Proxy.Network; 5 | 6 | /// 7 | /// An object that holds the cached certificate 8 | /// 9 | internal sealed class CachedCertificate 10 | { 11 | public CachedCertificate(X509Certificate2 certificate) 12 | { 13 | Certificate = certificate; 14 | } 15 | 16 | internal X509Certificate2 Certificate { get; } 17 | 18 | /// 19 | /// Last time this certificate was used. 20 | /// Useful in determining its cache lifetime. 21 | /// 22 | internal DateTime LastAccess { get; set; } 23 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Certificates/Cache/ICertificateCache.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography.X509Certificates; 2 | 3 | namespace Titanium.Web.Proxy.Network; 4 | 5 | public interface ICertificateCache 6 | { 7 | /// 8 | /// Loads the root certificate from the storage. 9 | /// 10 | X509Certificate2? LoadRootCertificate(string pathOrName, string password, X509KeyStorageFlags storageFlags); 11 | 12 | /// 13 | /// Saves the root certificate to the storage. 14 | /// 15 | void SaveRootCertificate(string pathOrName, string password, X509Certificate2 certificate); 16 | 17 | /// 18 | /// Loads certificate from the storage. Returns true if certificate does not exist. 19 | /// 20 | X509Certificate2? LoadCertificate(string subjectName, X509KeyStorageFlags storageFlags); 21 | 22 | /// 23 | /// Stores certificate into the storage. 24 | /// 25 | void SaveCertificate(string subjectName, X509Certificate2 certificate); 26 | 27 | /// 28 | /// Clears the storage. 29 | /// 30 | void Clear(); 31 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Certificates/Makers/ICertificateMaker.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography.X509Certificates; 2 | 3 | namespace Titanium.Web.Proxy.Network.Certificate; 4 | 5 | /// 6 | /// Abstract interface for different Certificate Maker Engines 7 | /// 8 | internal interface ICertificateMaker 9 | { 10 | X509Certificate2 MakeCertificate(string sSubjectCn, X509Certificate2? signingCert); 11 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Compression/CompressionFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | 5 | namespace Titanium.Web.Proxy.Compression 6 | { 7 | /// 8 | /// A factory to generate the compression methods based on the type of compression 9 | /// 10 | internal static class CompressionFactory 11 | { 12 | internal static Stream Create(HttpCompression type, Stream stream, bool leaveOpen = true) 13 | { 14 | return type switch 15 | { 16 | HttpCompression.Gzip => new GZipStream(stream, CompressionMode.Compress, leaveOpen), 17 | HttpCompression.Deflate => new DeflateStream(stream, CompressionMode.Compress, leaveOpen), 18 | HttpCompression.Brotli => new BrotliSharpLib.BrotliStream(stream, CompressionMode.Compress, leaveOpen), 19 | _ => throw new Exception($"Unsupported compression mode: {type}") 20 | }; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Compression/DecompressionFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | 5 | namespace Titanium.Web.Proxy.Compression; 6 | 7 | /// 8 | /// A factory to generate the de-compression methods based on the type of compression 9 | /// 10 | internal class DecompressionFactory 11 | { 12 | internal static Stream Create(HttpCompression type, Stream stream, bool leaveOpen = true) 13 | { 14 | return type switch 15 | { 16 | HttpCompression.Gzip => new GZipStream(stream, CompressionMode.Decompress, leaveOpen), 17 | HttpCompression.Deflate => new DeflateStream(stream, CompressionMode.Decompress, leaveOpen), 18 | HttpCompression.Brotli => new BrotliSharpLib.BrotliStream(stream, CompressionMode.Decompress, leaveOpen), 19 | _ => throw new Exception($"Unsupported decompression mode: {type}") 20 | }; 21 | } 22 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/EventArguments/BeforeBodyWriteEventArgs.cs: -------------------------------------------------------------------------------- 1 | #if DEBUG 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Titanium.Web.Proxy.EventArguments 9 | { 10 | 11 | public class BeforeBodyWriteEventArgs : ProxyEventArgsBase 12 | { 13 | internal BeforeBodyWriteEventArgs(SessionEventArgs session, byte[] bodyBytes, bool isChunked, bool isLastChunk) : base(session.Server, session.ClientConnection) 14 | { 15 | Session = session; 16 | BodyBytes = bodyBytes; 17 | IsChunked = isChunked; 18 | IsLastChunk = isLastChunk; 19 | } 20 | 21 | 22 | /// 23 | /// The session arguments. 24 | /// 25 | public SessionEventArgs Session { get; } 26 | 27 | /// 28 | /// Indicates whether body is written chunked stream. 29 | /// If this is true, BeforeRequestBodySend or BeforeResponseBodySend will be called until IsLastChunk is false. 30 | /// 31 | public bool IsChunked { get; } 32 | 33 | /// 34 | /// Indicates if this is the last chunk from client or server stream, when request is chunked. 35 | /// Override this property to true if there are more bytes to write. 36 | /// 37 | public bool IsLastChunk { get; set; } 38 | 39 | /// 40 | /// The bytes about to be written. If IsChunked is true, this will be a chunk of the bytes to be written. 41 | /// Override this property with custom bytes if needed, and adjust IsLastChunk accordingly. 42 | /// 43 | public byte[] BodyBytes { get; set; } 44 | } 45 | } 46 | #endif -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/EventArguments/BeforeSslAuthenticateEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using Titanium.Web.Proxy.Network.Tcp; 3 | 4 | namespace Titanium.Web.Proxy.EventArguments; 5 | 6 | /// 7 | /// This is used in transparent endpoint before authenticating client. 8 | /// 9 | public class BeforeSslAuthenticateEventArgs : ProxyEventArgsBase 10 | { 11 | internal readonly CancellationTokenSource TaskCancellationSource; 12 | 13 | internal BeforeSslAuthenticateEventArgs(ProxyServer server, TcpClientConnection clientConnection, 14 | CancellationTokenSource taskCancellationSource, string sniHostName) : base(server, clientConnection) 15 | { 16 | TaskCancellationSource = taskCancellationSource; 17 | SniHostName = sniHostName; 18 | ForwardHttpsHostName = sniHostName; 19 | } 20 | 21 | /// 22 | /// The server name indication hostname if available. 23 | /// Otherwise the GenericCertificateName property of TransparentEndPoint. 24 | /// 25 | public string SniHostName { get; } 26 | 27 | /// 28 | /// Should we decrypt the SSL request? 29 | /// If true we decrypt with fake certificate. 30 | /// If false we relay the connection to the hostname mentioned in SniHostname. 31 | /// 32 | public bool DecryptSsl { get; set; } = true; 33 | 34 | /// 35 | /// We need to know the server hostname we are forwarding the request to. 36 | /// By default its the SNI hostname indicated in SSL handshake, when SNI is available. 37 | /// When SNI is not available, it will use the GenericCertificateName of TransparentEndPoint. 38 | /// This property is used only when DecryptSsl or when BeforeSslAuthenticateEventArgs.DecryptSsl is false. 39 | /// When DecryptSsl is true, we need to explicitly set the Forwarded host and port by setting 40 | /// e.HttpClient.Request.Url inside BeforeRequest event handler. 41 | /// 42 | public string ForwardHttpsHostName { get; set; } 43 | 44 | /// 45 | /// We need to know the server port we are forwarding the request to. 46 | /// By default its the standard https port, 443. 47 | /// This property is used only when DecryptSsl or when BeforeSslAuthenticateEventArgs.DecryptSsl is false. 48 | /// When DecryptSsl is true, we need to explicitly set the Forwarded host and port by setting 49 | /// e.HttpClient.Request.Url inside BeforeRequest event handler. 50 | /// 51 | public int ForwardHttpsPort { get; set; } = 443; 52 | 53 | /// 54 | /// Terminate the request abruptly by closing client/server connections. 55 | /// 56 | public void TerminateSession() 57 | { 58 | TaskCancellationSource.Cancel(); 59 | } 60 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/EventArguments/CertificateSelectionEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography.X509Certificates; 2 | 3 | namespace Titanium.Web.Proxy.EventArguments; 4 | 5 | /// 6 | /// An argument passed on to user for client certificate selection during mutual SSL authentication. 7 | /// 8 | public class CertificateSelectionEventArgs : ProxyEventArgsBase 9 | { 10 | public CertificateSelectionEventArgs(SessionEventArgsBase session, string targetHost, 11 | X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers) : 12 | base(session.Server, session.ClientConnection) 13 | { 14 | Session = session; 15 | TargetHost = targetHost; 16 | LocalCertificates = localCertificates; 17 | RemoteCertificate = remoteCertificate; 18 | AcceptableIssuers = acceptableIssuers; 19 | } 20 | 21 | /// 22 | /// The session. 23 | /// 24 | public SessionEventArgsBase Session { get; } 25 | 26 | /// 27 | /// The remote hostname to which we are authenticating against. 28 | /// 29 | public string TargetHost { get; } 30 | 31 | /// 32 | /// Local certificates in store with matching issuers requested by TargetHost website. 33 | /// 34 | public X509CertificateCollection LocalCertificates { get; } 35 | 36 | /// 37 | /// Certificate of the remote server. 38 | /// 39 | public X509Certificate RemoteCertificate { get; } 40 | 41 | /// 42 | /// Acceptable issuers as listed by remote server. 43 | /// 44 | public string[] AcceptableIssuers { get; } 45 | 46 | /// 47 | /// Client Certificate we selected. Set this value to override. 48 | /// 49 | public X509Certificate? ClientCertificate { get; set; } 50 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/EventArguments/CertificateValidationEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Security; 2 | using System.Security.Cryptography.X509Certificates; 3 | 4 | namespace Titanium.Web.Proxy.EventArguments; 5 | 6 | /// 7 | /// An argument passed on to the user for validating the server certificate 8 | /// during SSL authentication. 9 | /// 10 | public class CertificateValidationEventArgs : ProxyEventArgsBase 11 | { 12 | public CertificateValidationEventArgs(SessionEventArgsBase session, X509Certificate certificate, X509Chain chain, 13 | SslPolicyErrors sslPolicyErrors) : base(session.Server, session.ClientConnection) 14 | { 15 | Session = session; 16 | Certificate = certificate; 17 | Chain = chain; 18 | SslPolicyErrors = sslPolicyErrors; 19 | } 20 | 21 | /// 22 | /// The session. 23 | /// 24 | public SessionEventArgsBase Session { get; } 25 | 26 | /// 27 | /// Server certificate. 28 | /// 29 | public X509Certificate Certificate { get; } 30 | 31 | /// 32 | /// Certificate chain. 33 | /// 34 | public X509Chain Chain { get; } 35 | 36 | /// 37 | /// SSL policy errors. 38 | /// 39 | public SslPolicyErrors SslPolicyErrors { get; } 40 | 41 | /// 42 | /// Is the given server certificate valid? 43 | /// 44 | public bool IsValid { get; set; } 45 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/EventArguments/DataEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Titanium.Web.Proxy.StreamExtended.Network; 4 | 5 | /// 6 | /// Wraps the data sent/received event argument. 7 | /// 8 | public class DataEventArgs : EventArgs 9 | { 10 | public DataEventArgs(byte[] buffer, int offset, int count) 11 | { 12 | Buffer = buffer; 13 | Offset = offset; 14 | Count = count; 15 | } 16 | 17 | /// 18 | /// The buffer with data. 19 | /// 20 | public byte[] Buffer { get; } 21 | 22 | /// 23 | /// Offset in buffer from which valid data begins. 24 | /// 25 | public int Offset { get; } 26 | 27 | /// 28 | /// Length from offset in buffer with valid data. 29 | /// 30 | public int Count { get; } 31 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/EventArguments/EmptyProxyEventArgs.cs: -------------------------------------------------------------------------------- 1 | using Titanium.Web.Proxy.Network.Tcp; 2 | 3 | namespace Titanium.Web.Proxy.EventArguments; 4 | 5 | public class EmptyProxyEventArgs : ProxyEventArgsBase 6 | { 7 | internal EmptyProxyEventArgs(ProxyServer server, TcpClientConnection clientConnection) : base(server, 8 | clientConnection) 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/EventArguments/MultipartRequestPartSentEventArgs.cs: -------------------------------------------------------------------------------- 1 | using Titanium.Web.Proxy.Http; 2 | 3 | namespace Titanium.Web.Proxy.EventArguments; 4 | 5 | /// 6 | /// Class that wraps the multipart sent request arguments. 7 | /// 8 | public class MultipartRequestPartSentEventArgs : ProxyEventArgsBase 9 | { 10 | internal MultipartRequestPartSentEventArgs(SessionEventArgs session, string boundary, HeaderCollection headers) : 11 | base(session.Server, session.ClientConnection) 12 | { 13 | Session = session; 14 | Boundary = boundary; 15 | Headers = headers; 16 | } 17 | 18 | /// 19 | /// The session arguments. 20 | /// 21 | public SessionEventArgs Session { get; } 22 | 23 | /// 24 | /// Boundary. 25 | /// 26 | public string Boundary { get; } 27 | 28 | /// 29 | /// The header collection. 30 | /// 31 | public HeaderCollection Headers { get; } 32 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/EventArguments/ProxyEventArgsBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Titanium.Web.Proxy.Network.Tcp; 3 | 4 | namespace Titanium.Web.Proxy.EventArguments; 5 | 6 | /// 7 | /// The base event arguments 8 | /// 9 | /// 10 | public abstract class ProxyEventArgsBase : EventArgs 11 | { 12 | private readonly TcpClientConnection clientConnection; 13 | internal readonly ProxyServer Server; 14 | 15 | internal ProxyEventArgsBase(ProxyServer server, TcpClientConnection clientConnection) 16 | { 17 | this.clientConnection = clientConnection; 18 | Server = server; 19 | } 20 | 21 | public object ClientUserData 22 | { 23 | get => clientConnection.ClientUserData; 24 | set => clientConnection.ClientUserData = value; 25 | } 26 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/EventArguments/TransformationMode.cs: -------------------------------------------------------------------------------- 1 | namespace Titanium.Web.Proxy.EventArguments; 2 | 3 | internal enum TransformationMode 4 | { 5 | None, 6 | 7 | /// 8 | /// Removes the chunked encoding 9 | /// 10 | RemoveChunked, 11 | 12 | /// 13 | /// Uncompress the body (this also removes the chunked encoding if exists) 14 | /// 15 | Uncompress 16 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/EventArguments/TunnelConnectEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Titanium.Web.Proxy.Helpers; 4 | using Titanium.Web.Proxy.Http; 5 | using Titanium.Web.Proxy.Models; 6 | using Titanium.Web.Proxy.StreamExtended.Network; 7 | 8 | namespace Titanium.Web.Proxy.EventArguments; 9 | 10 | /// 11 | /// A class that wraps the state when a tunnel connect event happen for Explicit endpoints. 12 | /// 13 | public class TunnelConnectSessionEventArgs : SessionEventArgsBase 14 | { 15 | private bool? isHttpsConnect; 16 | 17 | internal TunnelConnectSessionEventArgs(ProxyServer server, ProxyEndPoint endPoint, ConnectRequest connectRequest, 18 | HttpClientStream clientStream, CancellationTokenSource cancellationTokenSource) 19 | : base(server, endPoint, clientStream, connectRequest, connectRequest, cancellationTokenSource) 20 | { 21 | } 22 | 23 | /// 24 | /// Should we decrypt the Ssl or relay it to server? 25 | /// Default is true. 26 | /// 27 | public bool DecryptSsl { get; set; } = true; 28 | 29 | /// 30 | /// When set to true it denies the connect request with a Forbidden status. 31 | /// 32 | public bool DenyConnect { get; set; } 33 | 34 | /// 35 | /// Is this a connect request to secure HTTP server? Or is it to some other protocol. 36 | /// 37 | public bool IsHttpsConnect 38 | { 39 | get => isHttpsConnect ?? 40 | throw new Exception("The value of this property is known in the BeforeTunnelConnectResponse event"); 41 | 42 | internal set => isHttpsConnect = value; 43 | } 44 | 45 | /// 46 | /// Fired when decrypted data is sent within this session to server/client. 47 | /// 48 | public event EventHandler? DecryptedDataSent; 49 | 50 | /// 51 | /// Fired when decrypted data is received within this session from client/server. 52 | /// 53 | public event EventHandler? DecryptedDataReceived; 54 | 55 | internal void OnDecryptedDataSent(byte[] buffer, int offset, int count) 56 | { 57 | try 58 | { 59 | DecryptedDataSent?.Invoke(this, new DataEventArgs(buffer, offset, count)); 60 | } 61 | catch (Exception ex) 62 | { 63 | OnException(new Exception("Exception thrown in user event", ex)); 64 | } 65 | } 66 | 67 | internal void OnDecryptedDataReceived(byte[] buffer, int offset, int count) 68 | { 69 | try 70 | { 71 | DecryptedDataReceived?.Invoke(this, new DataEventArgs(buffer, offset, count)); 72 | } 73 | catch (Exception ex) 74 | { 75 | OnException(new Exception("Exception thrown in user event", ex)); 76 | } 77 | } 78 | 79 | ~TunnelConnectSessionEventArgs() 80 | { 81 | #if DEBUG 82 | // Finalizer should not be called 83 | System.Diagnostics.Debugger.Break(); 84 | #endif 85 | 86 | Dispose(false); 87 | } 88 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Exceptions/BodyNotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace Titanium.Web.Proxy.Exceptions; 2 | 3 | /// 4 | /// An exception thrown when body is unexpectedly empty. 5 | /// 6 | public class BodyNotFoundException : ProxyException 7 | { 8 | /// 9 | /// Constructor. 10 | /// 11 | /// 12 | internal BodyNotFoundException(string message) : base(message) 13 | { 14 | } 15 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Exceptions/ProxyAuthorizationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Titanium.Web.Proxy.EventArguments; 4 | using Titanium.Web.Proxy.Models; 5 | 6 | namespace Titanium.Web.Proxy.Exceptions; 7 | 8 | /// 9 | /// Proxy authorization exception. 10 | /// 11 | public class ProxyAuthorizationException : ProxyException 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// Exception message. 17 | /// The instance containing the event data. 18 | /// Inner exception associated to upstream proxy authorization 19 | /// Http's headers associated 20 | internal ProxyAuthorizationException(string message, SessionEventArgsBase session, Exception innerException, 21 | IEnumerable headers) : base(message, innerException) 22 | { 23 | Session = session; 24 | Headers = headers; 25 | } 26 | 27 | /// 28 | /// The current session within which this error happened. 29 | /// 30 | public SessionEventArgsBase Session { get; } 31 | 32 | /// 33 | /// Headers associated with the authorization exception. 34 | /// 35 | public IEnumerable Headers { get; } 36 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Exceptions/ProxyConnectException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Titanium.Web.Proxy.EventArguments; 3 | 4 | namespace Titanium.Web.Proxy.Exceptions; 5 | 6 | /// 7 | /// Proxy Connection exception. 8 | /// 9 | public class ProxyConnectException : ProxyException 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// Message for this exception 15 | /// Associated inner exception 16 | /// 17 | /// Instance of associated to the 18 | /// exception 19 | /// 20 | internal ProxyConnectException(string message, Exception innerException, SessionEventArgsBase session) : base( 21 | message, innerException) 22 | { 23 | Session = session; 24 | } 25 | 26 | /// 27 | /// Gets session info associated to the exception. 28 | /// 29 | /// 30 | /// This object properties should not be edited. 31 | /// 32 | public SessionEventArgsBase Session { get; } 33 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Exceptions/ProxyException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Titanium.Web.Proxy.Exceptions; 4 | 5 | /// 6 | /// Base class exception associated with this proxy server. 7 | /// 8 | public abstract class ProxyException : Exception 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// - must be invoked by derived classes' constructors 13 | /// 14 | /// Exception message 15 | protected ProxyException(string message) : base(message) 16 | { 17 | } 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// - must be invoked by derived classes' constructors 22 | /// 23 | /// Exception message 24 | /// Inner exception associated 25 | protected ProxyException(string message, Exception? innerException) : base(message, innerException) 26 | { 27 | } 28 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Exceptions/ProxyHttpException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Titanium.Web.Proxy.EventArguments; 3 | 4 | namespace Titanium.Web.Proxy.Exceptions; 5 | 6 | /// 7 | /// Proxy HTTP exception. 8 | /// 9 | public class ProxyHttpException : ProxyException 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// Message for this exception 15 | /// Associated inner exception 16 | /// Instance of associated to the exception 17 | internal ProxyHttpException(string message, Exception? innerException, SessionEventArgs? session) : base( 18 | message, innerException) 19 | { 20 | Session = session; 21 | } 22 | 23 | /// 24 | /// Gets session info associated to the exception. 25 | /// 26 | /// 27 | /// This object properties should not be edited. 28 | /// 29 | public SessionEventArgs? Session { get; } 30 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Exceptions/RetryableServerConnectionException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Titanium.Web.Proxy.Exceptions; 4 | 5 | /// 6 | /// The server connection was closed upon first write with the new connection from pool. 7 | /// Should retry the request with a new connection. 8 | /// 9 | public class RetryableServerConnectionException : ProxyException 10 | { 11 | internal RetryableServerConnectionException(string message) : base(message) 12 | { 13 | } 14 | 15 | /// 16 | /// Constructor. 17 | /// 18 | /// 19 | /// 20 | internal RetryableServerConnectionException(string message, Exception e) : base(message, e) 21 | { 22 | } 23 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Extensions/FuncExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Titanium.Web.Proxy.EventArguments; 4 | 5 | namespace Titanium.Web.Proxy.Extensions; 6 | 7 | internal static class FuncExtensions 8 | { 9 | internal static async Task InvokeAsync(this AsyncEventHandler callback, object sender, T args, 10 | ExceptionHandler? exceptionFunc) 11 | { 12 | var invocationList = callback.GetInvocationList(); 13 | 14 | foreach (var @delegate in invocationList) 15 | await InternalInvokeAsync((AsyncEventHandler)@delegate, sender, args, exceptionFunc); 16 | } 17 | 18 | private static async Task InternalInvokeAsync(AsyncEventHandler callback, object sender, T args, 19 | ExceptionHandler? exceptionFunc) 20 | { 21 | try 22 | { 23 | await callback(sender, args); 24 | } 25 | catch (Exception e) 26 | { 27 | exceptionFunc?.Invoke(new Exception("Exception thrown in user event", e)); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Extensions/HttpHeaderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Titanium.Web.Proxy.Models; 3 | 4 | namespace Titanium.Web.Proxy.Extensions; 5 | 6 | internal static class HttpHeaderExtensions 7 | { 8 | internal static string GetString(this ByteString str) 9 | { 10 | return GetString(str.Span); 11 | } 12 | 13 | internal static string GetString(this ReadOnlySpan bytes) 14 | { 15 | #if NET6_0_OR_GREATER 16 | return HttpHeader.Encoding.GetString(bytes); 17 | #else 18 | return HttpHeader.Encoding.GetString(bytes.ToArray()); 19 | #endif 20 | } 21 | 22 | internal static ByteString GetByteString(this string str) 23 | { 24 | return HttpHeader.Encoding.GetBytes(str); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Extensions/StreamExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Titanium.Web.Proxy.StreamExtended.BufferPool; 6 | 7 | namespace Titanium.Web.Proxy.Extensions; 8 | 9 | /// 10 | /// Extensions used for Stream and CustomBinaryReader objects 11 | /// 12 | internal static class StreamExtensions 13 | { 14 | /// 15 | /// Copy streams asynchronously 16 | /// 17 | /// 18 | /// 19 | /// 20 | /// 21 | internal static Task CopyToAsync(this Stream input, Stream output, Action onCopy, 22 | IBufferPool bufferPool) 23 | { 24 | return CopyToAsync(input, output, onCopy, bufferPool, CancellationToken.None); 25 | } 26 | 27 | /// 28 | /// Copy streams asynchronously 29 | /// 30 | /// 31 | /// 32 | /// 33 | /// 34 | /// 35 | internal static async Task CopyToAsync(this Stream input, Stream output, Action? onCopy, 36 | IBufferPool bufferPool, CancellationToken cancellationToken) 37 | { 38 | var buffer = bufferPool.GetBuffer(); 39 | try 40 | { 41 | while (!cancellationToken.IsCancellationRequested) 42 | { 43 | // cancellation is not working on Socket ReadAsync 44 | // https://github.com/dotnet/corefx/issues/15033 45 | var num = await input.ReadAsync(buffer, 0, buffer.Length, cancellationToken) 46 | .WithCancellation(cancellationToken); 47 | int bytesRead; 48 | if ((bytesRead = num) != 0 && !cancellationToken.IsCancellationRequested) 49 | { 50 | await output.WriteAsync(buffer, 0, bytesRead, CancellationToken.None); 51 | onCopy?.Invoke(buffer, 0, bytesRead); 52 | } 53 | else 54 | { 55 | break; 56 | } 57 | } 58 | } 59 | finally 60 | { 61 | bufferPool.ReturnBuffer(buffer); 62 | } 63 | } 64 | 65 | internal static async Task WithCancellation(this Task task, CancellationToken cancellationToken) 66 | where T : struct 67 | { 68 | var tcs = new TaskCompletionSource(); 69 | using (cancellationToken.Register(s => ((TaskCompletionSource)s).TrySetResult(true), tcs)) 70 | { 71 | if (task != await Task.WhenAny(task, tcs.Task)) return default; 72 | } 73 | 74 | return await task; 75 | } 76 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers.Text; 3 | using System.Buffers; 4 | using System.Globalization; 5 | using System.Text; 6 | 7 | namespace Titanium.Web.Proxy.Extensions; 8 | 9 | internal static class StringExtensions 10 | { 11 | internal static bool EqualsIgnoreCase(this string str, string? value) 12 | { 13 | return str.Equals(value, StringComparison.CurrentCultureIgnoreCase); 14 | } 15 | 16 | internal static bool EqualsIgnoreCase(this ReadOnlySpan str, ReadOnlySpan value) 17 | { 18 | return str.Equals(value, StringComparison.CurrentCultureIgnoreCase); 19 | } 20 | 21 | internal static bool ContainsIgnoreCase(this string str, string? value) 22 | { 23 | return CultureInfo.CurrentCulture.CompareInfo.IndexOf(str, value, CompareOptions.IgnoreCase) >= 0; 24 | } 25 | 26 | internal static int IndexOfIgnoreCase(this string str, string? value) 27 | { 28 | return CultureInfo.CurrentCulture.CompareInfo.IndexOf(str, value, CompareOptions.IgnoreCase); 29 | } 30 | 31 | internal static unsafe string ByteArrayToHexString(this ReadOnlySpan data) 32 | { 33 | if (data.Length == 0) 34 | { 35 | return string.Empty; 36 | } 37 | 38 | int length = data.Length * 3; 39 | Span buf = stackalloc byte[length]; 40 | var buf2 = buf; 41 | foreach (var b in data) 42 | { 43 | Utf8Formatter.TryFormat(b, buf2, out _, new StandardFormat('X', 2)); 44 | buf2[2] = 32; // space 45 | buf2 = buf2.Slice(3); 46 | } 47 | 48 | #if NET6_0_OR_GREATER 49 | return Encoding.UTF8.GetString(buf.Slice(0, length - 1)); 50 | #else 51 | fixed (byte* bp = buf) 52 | { 53 | return Encoding.UTF8.GetString(bp, length -1); 54 | } 55 | #endif 56 | } 57 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Extensions/TcpExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Sockets; 2 | 3 | namespace Titanium.Web.Proxy.Extensions; 4 | 5 | internal static class TcpExtensions 6 | { 7 | /// 8 | /// Check if a TcpClient is good to be used. 9 | /// This only checks if send is working so local socket is still connected. 10 | /// Receive can only be verified by doing a valid read from server without exceptions. 11 | /// So in our case we should retry with new connection from pool if first read after getting the connection fails. 12 | /// https://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected(v=vs.110).aspx 13 | /// 14 | /// 15 | /// 16 | internal static bool IsGoodConnection(this Socket socket) 17 | { 18 | if (!socket.Connected) return false; 19 | 20 | // This is how you can determine whether a socket is still connected. 21 | var blockingState = socket.Blocking; 22 | try 23 | { 24 | var tmp = new byte[1]; 25 | 26 | socket.Blocking = false; 27 | socket.Send(tmp, 0, 0); 28 | // Connected. 29 | } 30 | catch 31 | { 32 | // Should we let 10035 == WSAEWOULDBLOCK as valid connection? 33 | return false; 34 | } 35 | finally 36 | { 37 | socket.Blocking = blockingState; 38 | } 39 | 40 | return true; 41 | } 42 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Extensions/UriExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Titanium.Web.Proxy.Models; 3 | 4 | namespace Titanium.Web.Proxy.Extensions; 5 | 6 | internal static class UriExtensions 7 | { 8 | public static string GetOriginalPathAndQuery(this Uri uri) 9 | { 10 | var leftPart = uri.GetLeftPart(UriPartial.Authority); 11 | if (uri.OriginalString.StartsWith(leftPart)) 12 | return uri.OriginalString.Substring(leftPart.Length); 13 | 14 | return uri.IsWellFormedOriginalString() 15 | ? uri.PathAndQuery 16 | : uri.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped); 17 | } 18 | 19 | public static ByteString GetScheme(ByteString str) 20 | { 21 | if (str.Length < 3) return ByteString.Empty; 22 | 23 | // regex: "^[a-z]*://" 24 | int i; 25 | 26 | for (i = 0; i < str.Length - 3; i++) 27 | { 28 | var ch = str[i]; 29 | if (ch == ':') break; 30 | 31 | if (ch < 'A' || ch > 'z' || ch > 'Z' && ch < 'a') // ASCII letter 32 | return ByteString.Empty; 33 | } 34 | 35 | if (str[i++] != ':') return ByteString.Empty; 36 | 37 | if (str[i++] != '/') return ByteString.Empty; 38 | 39 | if (str[i] != '/') return ByteString.Empty; 40 | 41 | return new ByteString(str.Data.Slice(0, i - 2)); 42 | } 43 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Handlers/AsyncEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Titanium.Web.Proxy.EventArguments; 4 | 5 | /// 6 | /// A generic asynchronous event handler used by the proxy. 7 | /// 8 | /// Event argument type. 9 | /// The proxy server instance. 10 | /// The event arguments. 11 | /// 12 | public delegate Task AsyncEventHandler(object sender, TEventArgs e); -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Handlers/CertificateHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Security; 3 | using System.Security.Cryptography.X509Certificates; 4 | using Titanium.Web.Proxy.EventArguments; 5 | using Titanium.Web.Proxy.Extensions; 6 | 7 | namespace Titanium.Web.Proxy; 8 | 9 | public partial class ProxyServer 10 | { 11 | /// 12 | /// Call back to override server certificate validation 13 | /// 14 | /// The sender object. 15 | /// The http session. 16 | /// The remote certificate. 17 | /// The certificate chain. 18 | /// Ssl policy errors 19 | /// Return true if valid certificate. 20 | internal bool ValidateServerCertificate(object sender, SessionEventArgsBase sessionArgs, 21 | X509Certificate certificate, X509Chain chain, 22 | SslPolicyErrors sslPolicyErrors) 23 | { 24 | // if user callback is registered then do it 25 | if (ServerCertificateValidationCallback != null) 26 | { 27 | var args = new CertificateValidationEventArgs(sessionArgs, certificate, chain, sslPolicyErrors); 28 | 29 | // why is the sender null? 30 | ServerCertificateValidationCallback.InvokeAsync(this, args, ExceptionFunc).Wait(); 31 | return args.IsValid; 32 | } 33 | 34 | if (sslPolicyErrors == SslPolicyErrors.None) return true; 35 | 36 | // By default 37 | // do not allow this client to communicate with unauthenticated servers. 38 | return false; 39 | } 40 | 41 | /// 42 | /// Call back to select client certificate used for mutual authentication 43 | /// 44 | /// The sender. 45 | /// The http session. 46 | /// The remote hostname. 47 | /// Selected local certificates by SslStream. 48 | /// The remote certificate of server. 49 | /// The acceptable issues for client certificate as listed by server. 50 | /// 51 | internal X509Certificate? SelectClientCertificate(object sender, SessionEventArgsBase sessionArgs, 52 | string targetHost, 53 | X509CertificateCollection localCertificates, 54 | X509Certificate remoteCertificate, string[] acceptableIssuers) 55 | { 56 | X509Certificate? clientCertificate = null; 57 | 58 | //fallback to the first client certificate from proxy machine certificate store 59 | if (acceptableIssuers != null && acceptableIssuers.Length > 0 && localCertificates != null && 60 | localCertificates.Count > 0) 61 | foreach (var certificate in localCertificates) 62 | { 63 | var issuer = certificate.Issuer; 64 | if (Array.IndexOf(acceptableIssuers, issuer) != -1) clientCertificate = certificate; 65 | } 66 | 67 | //fallback to the first client certificate from proxy machine certificate store 68 | if (clientCertificate == null 69 | && localCertificates != null && localCertificates.Count > 0) 70 | clientCertificate = localCertificates[0]; 71 | 72 | // If user call back is registered 73 | if (ClientCertificateSelectionCallback != null) 74 | { 75 | var args = new CertificateSelectionEventArgs(sessionArgs, targetHost, localCertificates, remoteCertificate, 76 | acceptableIssuers) 77 | { 78 | ClientCertificate = clientCertificate 79 | }; 80 | 81 | 82 | ClientCertificateSelectionCallback.InvokeAsync(this, args, ExceptionFunc).Wait(); 83 | return args.ClientCertificate; 84 | } 85 | 86 | return clientCertificate; 87 | } 88 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Handlers/ExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Titanium.Web.Proxy; 4 | 5 | /// 6 | /// A delegate to catch exceptions occuring in proxy. 7 | /// 8 | /// The exception occurred in proxy. 9 | public delegate void ExceptionHandler(Exception exception); -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Handlers/WebSocketHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Titanium.Web.Proxy.EventArguments; 4 | using Titanium.Web.Proxy.Helpers; 5 | using Titanium.Web.Proxy.Http; 6 | using Titanium.Web.Proxy.Network.Tcp; 7 | 8 | namespace Titanium.Web.Proxy; 9 | 10 | public partial class ProxyServer 11 | { 12 | /// 13 | /// Handle upgrade to websocket 14 | /// 15 | private async Task HandleWebSocketUpgrade(SessionEventArgs args, 16 | HttpClientStream clientStream, TcpServerConnection serverConnection, 17 | CancellationTokenSource cancellationTokenSource, CancellationToken cancellationToken) 18 | { 19 | await serverConnection.Stream.WriteRequestAsync(args.HttpClient.Request, cancellationToken); 20 | 21 | var httpStatus = await serverConnection.Stream.ReadResponseStatus(cancellationToken); 22 | 23 | var response = args.HttpClient.Response; 24 | response.HttpVersion = httpStatus.Version; 25 | response.StatusCode = httpStatus.StatusCode; 26 | response.StatusDescription = httpStatus.Description; 27 | 28 | await HeaderParser.ReadHeaders(serverConnection.Stream, response.Headers, 29 | cancellationToken); 30 | 31 | await clientStream.WriteResponseAsync(response, cancellationToken); 32 | 33 | // If user requested call back then do it 34 | if (!args.HttpClient.Response.Locked) await OnBeforeResponse(args); 35 | 36 | await TcpHelper.SendRaw(clientStream, serverConnection.Stream, BufferPool, 37 | args.OnDataSent, args.OnDataReceived, cancellationTokenSource, ExceptionFunc); 38 | } 39 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Helpers/CompressionUtil.cs: -------------------------------------------------------------------------------- 1 | using Titanium.Web.Proxy.Http; 2 | 3 | namespace Titanium.Web.Proxy.Compression; 4 | 5 | internal static class CompressionUtil 6 | { 7 | public static HttpCompression CompressionNameToEnum(string name) 8 | { 9 | if (KnownHeaders.ContentEncodingGzip.Equals(name)) 10 | return HttpCompression.Gzip; 11 | 12 | if (KnownHeaders.ContentEncodingDeflate.Equals(name)) 13 | return HttpCompression.Deflate; 14 | 15 | if (KnownHeaders.ContentEncodingBrotli.Equals(name)) 16 | return HttpCompression.Brotli; 17 | 18 | return HttpCompression.Unsupported; 19 | } 20 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Helpers/NativeMethods.SystemProxy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Titanium.Web.Proxy.Helpers; 5 | 6 | internal partial class NativeMethods 7 | { 8 | // Keeps it from getting garbage collected 9 | internal static ConsoleEventDelegate? Handler; 10 | 11 | [DllImport("wininet.dll")] 12 | internal static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, 13 | int dwBufferLength); 14 | 15 | [DllImport("kernel32.dll")] 16 | internal static extern IntPtr GetConsoleWindow(); 17 | 18 | [DllImport("kernel32.dll", SetLastError = true)] 19 | internal static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate callback, bool add); 20 | 21 | /// 22 | /// 23 | /// 24 | [DllImport("user32.dll")] 25 | internal static extern int GetSystemMetrics(int nIndex); 26 | 27 | // Pinvoke 28 | internal delegate bool ConsoleEventDelegate(int eventType); 29 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Helpers/NativeMethods.Tcp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.NetworkInformation; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace Titanium.Web.Proxy.Helpers; 6 | 7 | internal partial class NativeMethods 8 | { 9 | internal const int AfInet = 2; 10 | internal const int AfInet6 = 23; 11 | 12 | /// 13 | /// 14 | /// 15 | [DllImport("iphlpapi.dll", SetLastError = true)] 16 | internal static extern uint GetExtendedTcpTable(IntPtr tcpTable, ref int size, bool sort, int ipVersion, 17 | int tableClass, int reserved); 18 | 19 | internal enum TcpTableType 20 | { 21 | BasicListener, 22 | BasicConnections, 23 | BasicAll, 24 | OwnerPidListener, 25 | OwnerPidConnections, 26 | OwnerPidAll, 27 | OwnerModuleListener, 28 | OwnerModuleConnections, 29 | OwnerModuleAll 30 | } 31 | 32 | /// 33 | /// 34 | /// 35 | [StructLayout(LayoutKind.Sequential)] 36 | internal struct TcpRow 37 | { 38 | public TcpState state; 39 | public uint localAddr; 40 | public uint localPort; // in network byte order (order of bytes - 1,0,3,2) 41 | public uint remoteAddr; 42 | public uint remotePort; // in network byte order (order of bytes - 1,0,3,2) 43 | public int owningPid; 44 | } 45 | 46 | /// 47 | /// 48 | /// 49 | [StructLayout(LayoutKind.Sequential)] 50 | internal unsafe struct Tcp6Row 51 | { 52 | public fixed byte localAddr[16]; 53 | public uint localScopeId; 54 | public uint localPort; // in network byte order (order of bytes - 1,0,3,2) 55 | public fixed byte remoteAddr[16]; 56 | public uint remoteScopeId; 57 | public uint remotePort; // in network byte order (order of bytes - 1,0,3,2) 58 | public TcpState state; 59 | public int owningPid; 60 | } 61 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Helpers/Net45Compatibility.cs: -------------------------------------------------------------------------------- 1 | #if NET451 2 | using System.Threading.Tasks; 3 | 4 | namespace Titanium.Web.Proxy 5 | { 6 | internal class Net45Compatibility 7 | { 8 | public static byte[] EmptyArray = new byte[0]; 9 | 10 | public static Task CompletedTask = Task.FromResult(null); 11 | } 12 | } 13 | #endif -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Helpers/Network.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Net; 4 | using System.Net.Sockets; 5 | 6 | namespace Titanium.Web.Proxy.Helpers; 7 | 8 | internal class NetworkHelper 9 | { 10 | private static readonly string localhostName = Dns.GetHostName(); 11 | private static readonly IPHostEntry localhostEntry = Dns.GetHostEntry(string.Empty); 12 | 13 | /// 14 | /// Adapated from below link 15 | /// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost 16 | /// 17 | /// 18 | /// 19 | internal static bool IsLocalIpAddress(IPAddress address) 20 | { 21 | if (IPAddress.IsLoopback(address)) return true; 22 | 23 | // test if host IP equals any local IP 24 | return localhostEntry.AddressList.Contains(address); 25 | } 26 | 27 | internal static bool IsLocalIpAddress(string hostName, bool proxyDnsRequests = false) 28 | { 29 | if (IPAddress.TryParse(hostName, out var ipAddress) 30 | && IsLocalIpAddress(ipAddress)) 31 | return true; 32 | 33 | if (hostName.Equals("localhost", StringComparison.OrdinalIgnoreCase)) return true; 34 | 35 | // if hostname matches local host name 36 | if (hostName.Equals(localhostName, StringComparison.OrdinalIgnoreCase)) return true; 37 | 38 | // if hostname matches fully qualified local DNS name 39 | if (hostName.Equals(localhostEntry.HostName, StringComparison.OrdinalIgnoreCase)) return true; 40 | 41 | if (!proxyDnsRequests) 42 | try 43 | { 44 | // do reverse DNS lookup even if hostName is an IP address 45 | var hostEntry = Dns.GetHostEntry(hostName); 46 | // if DNS resolved hostname matches local DNS name, 47 | // or if host IP address list contains any local IP address 48 | if (hostEntry.HostName.Equals(localhostEntry.HostName, StringComparison.OrdinalIgnoreCase) 49 | || hostEntry.AddressList.Any(hostIp => localhostEntry.AddressList.Contains(hostIp))) 50 | return true; 51 | } 52 | catch (SocketException) 53 | { 54 | } 55 | 56 | return false; 57 | } 58 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Helpers/WinHttp/WinHttpHandle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Titanium.Web.Proxy.Helpers.WinHttp; 5 | 6 | internal class WinHttpHandle : SafeHandle 7 | { 8 | public WinHttpHandle() : base(IntPtr.Zero, true) 9 | { 10 | } 11 | 12 | public override bool IsInvalid => handle == IntPtr.Zero; 13 | 14 | protected override bool ReleaseHandle() 15 | { 16 | return NativeMethods.WinHttp.WinHttpCloseHandle(handle); 17 | } 18 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Http/ConnectRequest.cs: -------------------------------------------------------------------------------- 1 | using Titanium.Web.Proxy.Models; 2 | using Titanium.Web.Proxy.StreamExtended; 3 | 4 | namespace Titanium.Web.Proxy.Http; 5 | 6 | /// 7 | /// The tcp tunnel Connect request. 8 | /// 9 | public class ConnectRequest : Request 10 | { 11 | internal ConnectRequest(ByteString authority) 12 | { 13 | Method = "CONNECT"; 14 | Authority = authority; 15 | } 16 | 17 | public TunnelType TunnelType { get; internal set; } 18 | 19 | public ClientHelloInfo? ClientHelloInfo { get; set; } 20 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Http/ConnectResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using Titanium.Web.Proxy.StreamExtended; 4 | 5 | namespace Titanium.Web.Proxy.Http; 6 | 7 | /// 8 | /// The tcp tunnel connect response object. 9 | /// 10 | public class ConnectResponse : Response 11 | { 12 | public ServerHelloInfo? ServerHelloInfo { get; set; } 13 | 14 | /// 15 | /// Creates a successful CONNECT response 16 | /// 17 | /// 18 | /// 19 | internal static ConnectResponse CreateSuccessfulConnectResponse(Version httpVersion) 20 | { 21 | var response = new ConnectResponse 22 | { 23 | HttpVersion = httpVersion, 24 | StatusCode = (int)HttpStatusCode.OK, 25 | StatusDescription = "Connection Established" 26 | }; 27 | 28 | return response; 29 | } 30 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Http/HeaderBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.IO; 4 | using System.Text; 5 | using Titanium.Web.Proxy.Models; 6 | using Titanium.Web.Proxy.Shared; 7 | 8 | namespace Titanium.Web.Proxy.Http; 9 | 10 | internal class HeaderBuilder 11 | { 12 | private readonly MemoryStream stream = new(); 13 | 14 | public void WriteRequestLine(string httpMethod, string httpUrl, Version version) 15 | { 16 | // "{httpMethod} {httpUrl} HTTP/{version.Major}.{version.Minor}"; 17 | 18 | Write(httpMethod); 19 | Write(" "); 20 | Write(httpUrl); 21 | Write(" HTTP/"); 22 | Write(version.Major.ToString()); 23 | Write("."); 24 | Write(version.Minor.ToString()); 25 | WriteLine(); 26 | } 27 | 28 | public void WriteResponseLine(Version version, int statusCode, string statusDescription) 29 | { 30 | // "HTTP/{version.Major}.{version.Minor} {statusCode} {statusDescription}"; 31 | 32 | Write("HTTP/"); 33 | Write(version.Major.ToString()); 34 | Write("."); 35 | Write(version.Minor.ToString()); 36 | Write(" "); 37 | Write(statusCode.ToString()); 38 | Write(" "); 39 | Write(statusDescription); 40 | WriteLine(); 41 | } 42 | 43 | public void WriteHeaders(HeaderCollection headers, bool sendProxyAuthorization = true, 44 | string? upstreamProxyUserName = null, string? upstreamProxyPassword = null) 45 | { 46 | if (upstreamProxyUserName != null && upstreamProxyPassword != null) 47 | { 48 | WriteHeader(HttpHeader.ProxyConnectionKeepAlive); 49 | WriteHeader(HttpHeader.GetProxyAuthorizationHeader(upstreamProxyUserName, upstreamProxyPassword)); 50 | } 51 | 52 | foreach (var header in headers) 53 | if (sendProxyAuthorization || !KnownHeaders.ProxyAuthorization.Equals(header.Name)) 54 | WriteHeader(header); 55 | 56 | WriteLine(); 57 | } 58 | 59 | public void WriteHeader(HttpHeader header) 60 | { 61 | Write(header.Name); 62 | Write(": "); 63 | Write(header.Value); 64 | WriteLine(); 65 | } 66 | 67 | public void WriteLine() 68 | { 69 | var data = ProxyConstants.NewLineBytes; 70 | stream.Write(data, 0, data.Length); 71 | } 72 | 73 | public void Write(string str) 74 | { 75 | var encoding = HttpHeader.Encoding; 76 | 77 | #if NET6_0_OR_GREATER 78 | var buf = ArrayPool.Shared.Rent(encoding.GetMaxByteCount(str.Length)); 79 | var span = new Span(buf); 80 | 81 | int bytes = encoding.GetBytes(str.AsSpan(), span); 82 | 83 | stream.Write(span.Slice(0, bytes)); 84 | ArrayPool.Shared.Return(buf); 85 | #else 86 | var data = encoding.GetBytes(str); 87 | stream.Write(data, 0, data.Length); 88 | #endif 89 | } 90 | 91 | public ArraySegment GetBuffer() 92 | { 93 | stream.TryGetBuffer(out var buffer); 94 | return buffer; 95 | } 96 | 97 | public string GetString(Encoding encoding) 98 | { 99 | stream.TryGetBuffer(out var buffer); 100 | return encoding.GetString(buffer.Array, buffer.Offset, buffer.Count); 101 | } 102 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Http/HeaderParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Titanium.Web.Proxy.StreamExtended.Network; 5 | 6 | namespace Titanium.Web.Proxy.Http; 7 | 8 | internal static class HeaderParser 9 | { 10 | internal static async ValueTask ReadHeaders(ILineStream reader, HeaderCollection headerCollection, 11 | CancellationToken cancellationToken) 12 | { 13 | string? tmpLine; 14 | while (!string.IsNullOrEmpty(tmpLine = await reader.ReadLineAsync(cancellationToken))) 15 | { 16 | var colonIndex = tmpLine!.IndexOf(':'); 17 | if (colonIndex == -1) throw new Exception("Header line should contain a colon character."); 18 | 19 | var headerName = tmpLine.AsSpan(0, colonIndex).ToString(); 20 | var headerValue = tmpLine.AsSpan(colonIndex + 1).TrimStart().ToString(); 21 | headerCollection.AddHeader(headerName, headerValue); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Http/InternalDataStore.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Titanium.Web.Proxy.Http; 4 | 5 | internal class InternalDataStore : Dictionary 6 | { 7 | public bool TryGetValueAs(string key, out T value) 8 | { 9 | var result = TryGetValue(key, out var value1); 10 | if (result) 11 | value = (T)value1; 12 | else 13 | // hack: https://stackoverflow.com/questions/54593923/nullable-reference-types-with-generic-return-type 14 | value = default!; 15 | 16 | return result; 17 | } 18 | 19 | public T GetAs(string key) 20 | { 21 | return (T)this[key]; 22 | } 23 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Http/KnownHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Titanium.Web.Proxy.Extensions; 3 | using Titanium.Web.Proxy.Models; 4 | 5 | namespace Titanium.Web.Proxy.Http; 6 | 7 | public class KnownHeader 8 | { 9 | public string String; 10 | internal ByteString String8; 11 | 12 | private KnownHeader(string str) 13 | { 14 | String8 = (ByteString)str; 15 | String = str; 16 | } 17 | 18 | public override string ToString() 19 | { 20 | return String; 21 | } 22 | 23 | internal bool Equals(ReadOnlySpan value) 24 | { 25 | return String.AsSpan().EqualsIgnoreCase(value); 26 | } 27 | 28 | internal bool Equals(string? value) 29 | { 30 | return String.EqualsIgnoreCase(value); 31 | } 32 | 33 | public static implicit operator KnownHeader(string str) 34 | { 35 | return new(str); 36 | } 37 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Http/KnownHeaders.cs: -------------------------------------------------------------------------------- 1 | namespace Titanium.Web.Proxy.Http; 2 | 3 | /// 4 | /// Well known http headers. 5 | /// 6 | public static class KnownHeaders 7 | { 8 | // Both 9 | public static KnownHeader Connection = "Connection"; 10 | public static KnownHeader ConnectionClose = "close"; 11 | public static KnownHeader ConnectionKeepAlive = "keep-alive"; 12 | 13 | public static KnownHeader ContentLength = "Content-Length"; 14 | public static KnownHeader ContentLengthHttp2 = "content-length"; 15 | 16 | public static KnownHeader ContentType = "Content-Type"; 17 | public static KnownHeader ContentTypeCharset = "charset"; 18 | public static KnownHeader ContentTypeBoundary = "boundary"; 19 | 20 | public static KnownHeader Upgrade = "Upgrade"; 21 | public static KnownHeader UpgradeWebsocket = "websocket"; 22 | 23 | // Request headers 24 | public static KnownHeader AcceptEncoding = "Accept-Encoding"; 25 | 26 | public static KnownHeader Authorization = "Authorization"; 27 | 28 | public static KnownHeader Expect = "Expect"; 29 | public static KnownHeader Expect100Continue = "100-continue"; 30 | 31 | public static KnownHeader Host = "Host"; 32 | 33 | public static KnownHeader ProxyAuthorization = "Proxy-Authorization"; 34 | public static KnownHeader ProxyAuthorizationBasic = "basic"; 35 | 36 | public static KnownHeader ProxyConnection = "Proxy-Connection"; 37 | public static KnownHeader ProxyConnectionClose = "close"; 38 | 39 | // Response headers 40 | public static KnownHeader ContentEncoding = "Content-Encoding"; 41 | public static KnownHeader ContentEncodingDeflate = "deflate"; 42 | public static KnownHeader ContentEncodingGzip = "gzip"; 43 | public static KnownHeader ContentEncodingBrotli = "br"; 44 | 45 | public static KnownHeader Location = "Location"; 46 | 47 | public static KnownHeader ProxyAuthenticate = "Proxy-Authenticate"; 48 | 49 | public static KnownHeader TransferEncoding = "Transfer-Encoding"; 50 | public static KnownHeader TransferEncodingChunked = "chunked"; 51 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Http/Responses/OkResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace Titanium.Web.Proxy.Http.Responses; 4 | 5 | /// 6 | /// The http 200 Ok response. 7 | /// 8 | public sealed class OkResponse : Response 9 | { 10 | /// 11 | /// Constructor. 12 | /// 13 | public OkResponse() 14 | { 15 | StatusCode = (int)HttpStatusCode.OK; 16 | StatusDescription = "OK"; 17 | } 18 | 19 | /// 20 | /// Constructor. 21 | /// 22 | public OkResponse(byte[] body) : this() 23 | { 24 | Body = body; 25 | } 26 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Http/Responses/RedirectResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace Titanium.Web.Proxy.Http.Responses; 4 | 5 | /// 6 | /// The http redirect response. 7 | /// 8 | public sealed class RedirectResponse : Response 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | public RedirectResponse() 14 | { 15 | StatusCode = (int)HttpStatusCode.Found; 16 | StatusDescription = "Found"; 17 | } 18 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Http/TunnelType.cs: -------------------------------------------------------------------------------- 1 | namespace Titanium.Web.Proxy.Http; 2 | 3 | public enum TunnelType 4 | { 5 | Unknown, 6 | Https, 7 | Websocket, 8 | Http2 9 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Http2/Hpack/HuffmanEncoder.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Twitter, Inc 3 | * This file is a derivative work modified by Ringo Leese 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using System; 19 | using System.IO; 20 | using Titanium.Web.Proxy.Models; 21 | 22 | namespace Titanium.Web.Proxy.Http2.Hpack; 23 | 24 | internal class HuffmanEncoder 25 | { 26 | /// 27 | /// Huffman Encoder 28 | /// 29 | public static readonly HuffmanEncoder Instance = new(); 30 | 31 | /// 32 | /// the Huffman codes indexed by symbol 33 | /// 34 | private readonly int[] codes = HpackUtil.HuffmanCodes; 35 | 36 | /// 37 | /// the length of each Huffman code 38 | /// 39 | private readonly byte[] lengths = HpackUtil.HuffmanCodeLengths; 40 | 41 | /// 42 | /// Compresses the input string literal using the Huffman coding. 43 | /// 44 | /// the output stream for the compressed data 45 | /// the string literal to be Huffman encoded 46 | /// 47 | /// if an I/O error occurs. In particular, an IOException may be thrown if the 48 | /// output stream has been closed. 49 | /// 50 | public void Encode(BinaryWriter output, ByteString data) 51 | { 52 | if (output == null) throw new ArgumentNullException(nameof(output)); 53 | 54 | if (data.Length == 0) return; 55 | 56 | var current = 0L; 57 | var n = 0; 58 | 59 | for (var i = 0; i < data.Length; i++) 60 | { 61 | var b = data.Span[i] & 0xFF; 62 | var code = (uint)codes[b]; 63 | int nbits = lengths[b]; 64 | 65 | current <<= nbits; 66 | current |= code; 67 | n += nbits; 68 | 69 | while (n >= 8) 70 | { 71 | n -= 8; 72 | output.Write((byte)(current >> n)); 73 | } 74 | } 75 | 76 | if (n > 0) 77 | { 78 | current <<= 8 - n; 79 | current |= (uint)(0xFF >> n); // this should be EOS symbol 80 | output.Write((byte)current); 81 | } 82 | } 83 | 84 | /// 85 | /// Returns the number of bytes required to Huffman encode the input string literal. 86 | /// 87 | /// the number of bytes required to Huffman encode data 88 | /// the string literal to be Huffman encoded 89 | public int GetEncodedLength(ByteString data) 90 | { 91 | var len = 0L; 92 | foreach (var b in data.Span) len += lengths[b]; 93 | 94 | return (int)((len + 7) >> 3); 95 | } 96 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Http2/Hpack/IHeaderListener.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Twitter, Inc 3 | * This file is a derivative work modified by Ringo Leese 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | using Titanium.Web.Proxy.Models; 19 | 20 | namespace Titanium.Web.Proxy.Http2.Hpack; 21 | 22 | internal interface IHeaderListener 23 | { 24 | /// 25 | /// EmitHeader is called by the decoder during header field emission. 26 | /// The name and value byte arrays must not be modified. 27 | /// 28 | /// Name. 29 | /// Value. 30 | /// If set to true sensitive. 31 | void AddHeader(ByteString name, ByteString value, bool sensitive); 32 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Http2/Http2FrameFlag.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Titanium.Web.Proxy.Http2; 4 | 5 | [Flags] 6 | internal enum Http2FrameFlag : byte 7 | { 8 | Ack = 0x01, 9 | EndStream = 0x01, 10 | EndHeaders = 0x04, 11 | Padded = 0x08, 12 | Priority = 0x20 13 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Http2/Http2FrameHeader.cs: -------------------------------------------------------------------------------- 1 | namespace Titanium.Web.Proxy.Http2; 2 | 3 | internal class Http2FrameHeader 4 | { 5 | public Http2FrameFlag Flags; 6 | public int Length; 7 | 8 | public int StreamId; 9 | 10 | public Http2FrameType Type; 11 | 12 | public void CopyToBuffer(byte[] buf) 13 | { 14 | var length = Length; 15 | buf[0] = (byte)((length >> 16) & 0xff); 16 | buf[1] = (byte)((length >> 8) & 0xff); 17 | buf[2] = (byte)(length & 0xff); 18 | buf[3] = (byte)Type; 19 | buf[4] = (byte)Flags; 20 | var streamId = StreamId; 21 | //buf[5] = (byte)((streamId >> 24) & 0xff); 22 | //buf[6] = (byte)((streamId >> 16) & 0xff); 23 | //buf[7] = (byte)((streamId >> 8) & 0xff); 24 | //buf[8] = (byte)(streamId & 0xff); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Http2/Http2FrameType.cs: -------------------------------------------------------------------------------- 1 | namespace Titanium.Web.Proxy.Http2; 2 | 3 | internal enum Http2FrameType : byte 4 | { 5 | Data = 0x00, 6 | Headers = 0x01, 7 | Priority = 0x02, 8 | RstStream = 0x03, 9 | Settings = 0x04, 10 | PushPromise = 0x05, 11 | Ping = 0x06, 12 | GoAway = 0x07, 13 | WindowUpdate = 0x08, 14 | Continuation = 0x09 15 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Models/ByteString.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using Titanium.Web.Proxy.Extensions; 4 | 5 | namespace Titanium.Web.Proxy.Models; 6 | 7 | internal struct ByteString : IEquatable 8 | { 9 | public static ByteString Empty = new(ReadOnlyMemory.Empty); 10 | 11 | public ReadOnlyMemory Data { get; } 12 | 13 | public ReadOnlySpan Span => Data.Span; 14 | 15 | public int Length => Data.Length; 16 | 17 | public ByteString(ReadOnlyMemory data) 18 | { 19 | Data = data; 20 | } 21 | 22 | public override bool Equals(object? obj) 23 | { 24 | return obj is ByteString other && Equals(other); 25 | } 26 | 27 | public bool Equals(ByteString other) 28 | { 29 | return Data.Span.SequenceEqual(other.Data.Span); 30 | } 31 | 32 | public int IndexOf(byte value) 33 | { 34 | return Span.IndexOf(value); 35 | } 36 | 37 | public ByteString Slice(int start) 38 | { 39 | return Data.Slice(start); 40 | } 41 | 42 | public ByteString Slice(int start, int length) 43 | { 44 | return Data.Slice(start, length); 45 | } 46 | 47 | public override int GetHashCode() 48 | { 49 | return Data.GetHashCode(); 50 | } 51 | 52 | public override string ToString() 53 | { 54 | return this.GetString(); 55 | } 56 | 57 | public static explicit operator ByteString(string str) 58 | { 59 | return new(Encoding.ASCII.GetBytes(str)); 60 | } 61 | 62 | public static implicit operator ByteString(byte[] data) 63 | { 64 | return new(data); 65 | } 66 | 67 | public static implicit operator ByteString(ReadOnlyMemory data) 68 | { 69 | return new(data); 70 | } 71 | 72 | public byte this[int i] => Span[i]; 73 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Models/ExplicitProxyEndPoint.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Net; 3 | using System.Threading.Tasks; 4 | using Titanium.Web.Proxy.EventArguments; 5 | using Titanium.Web.Proxy.Extensions; 6 | 7 | namespace Titanium.Web.Proxy.Models; 8 | 9 | /// 10 | /// A proxy endpoint that the client is aware of. 11 | /// So client application know that it is communicating with a proxy server. 12 | /// 13 | [DebuggerDisplay("Explicit: {IpAddress}:{Port}")] 14 | public class ExplicitProxyEndPoint : ProxyEndPoint 15 | { 16 | /// 17 | /// Constructor. 18 | /// 19 | /// Listening IP address. 20 | /// Listening port. 21 | /// Should we decrypt ssl? 22 | public ExplicitProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl = true) : base(ipAddress, port, 23 | decryptSsl) 24 | { 25 | } 26 | 27 | internal bool IsSystemHttpProxy { get; set; } 28 | 29 | internal bool IsSystemHttpsProxy { get; set; } 30 | 31 | /// 32 | /// Intercept tunnel connect request. 33 | /// Valid only for explicit endpoints. 34 | /// Set the property to false if this HTTP connect request 35 | /// shouldn't be decrypted and instead be relayed. 36 | /// 37 | public event AsyncEventHandler? BeforeTunnelConnectRequest; 38 | 39 | /// 40 | /// Intercept tunnel connect response. 41 | /// Valid only for explicit endpoints. 42 | /// 43 | public event AsyncEventHandler? BeforeTunnelConnectResponse; 44 | 45 | internal async Task InvokeBeforeTunnelConnectRequest(ProxyServer proxyServer, 46 | TunnelConnectSessionEventArgs connectArgs, ExceptionHandler? exceptionFunc) 47 | { 48 | if (BeforeTunnelConnectRequest != null) 49 | await BeforeTunnelConnectRequest.InvokeAsync(proxyServer, connectArgs, exceptionFunc); 50 | } 51 | 52 | internal async Task InvokeBeforeTunnelConnectResponse(ProxyServer proxyServer, 53 | TunnelConnectSessionEventArgs connectArgs, ExceptionHandler? exceptionFunc, bool isClientHello = false) 54 | { 55 | if (BeforeTunnelConnectResponse != null) 56 | { 57 | connectArgs.IsHttpsConnect = isClientHello; 58 | await BeforeTunnelConnectResponse.InvokeAsync(proxyServer, connectArgs, exceptionFunc); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Models/ExternalProxy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace Titanium.Web.Proxy.Models; 5 | 6 | /// 7 | /// An upstream proxy this proxy uses if any. 8 | /// 9 | public class ExternalProxy : IExternalProxy 10 | { 11 | private static readonly Lazy defaultCredentials = 12 | new(() => CredentialCache.DefaultNetworkCredentials); 13 | 14 | private string? password; 15 | 16 | private string? userName; 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | public ExternalProxy() 22 | { 23 | } 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// Name of the host. 29 | /// The port. 30 | public ExternalProxy(string hostName, int port) 31 | { 32 | HostName = hostName; 33 | Port = port; 34 | } 35 | 36 | /// 37 | /// Initializes a new instance of the class. 38 | /// 39 | /// Name of the host. 40 | /// The port. 41 | /// Name of the user. 42 | /// The password. 43 | public ExternalProxy(string hostName, int port, string userName, string password) 44 | { 45 | HostName = hostName; 46 | Port = port; 47 | UserName = userName; 48 | Password = password; 49 | } 50 | 51 | /// 52 | /// Use default windows credentials? 53 | /// 54 | public bool UseDefaultCredentials { get; set; } 55 | 56 | /// 57 | /// Bypass this proxy for connections to localhost? 58 | /// 59 | public bool BypassLocalhost { get; set; } 60 | 61 | public ExternalProxyType ProxyType { get; set; } 62 | 63 | public bool ProxyDnsRequests { get; set; } 64 | 65 | /// 66 | /// Username. 67 | /// 68 | public string? UserName 69 | { 70 | get => UseDefaultCredentials ? defaultCredentials.Value.UserName : userName; 71 | set 72 | { 73 | userName = value; 74 | 75 | if (defaultCredentials.Value.UserName != userName) UseDefaultCredentials = false; 76 | } 77 | } 78 | 79 | /// 80 | /// Password. 81 | /// 82 | public string? Password 83 | { 84 | get => UseDefaultCredentials ? defaultCredentials.Value.Password : password; 85 | set 86 | { 87 | password = value; 88 | 89 | if (defaultCredentials.Value.Password != password) UseDefaultCredentials = false; 90 | } 91 | } 92 | 93 | /// 94 | /// Host name. 95 | /// 96 | public string HostName { get; set; } = string.Empty; 97 | 98 | /// 99 | /// Port. 100 | /// 101 | public int Port { get; set; } 102 | 103 | /// 104 | /// returns data in Hostname:port format. 105 | /// 106 | /// 107 | public override string ToString() 108 | { 109 | return $"{HostName}:{Port}"; 110 | } 111 | } 112 | 113 | public enum ExternalProxyType 114 | { 115 | /// A HTTP/HTTPS proxy server. 116 | Http, 117 | 118 | /// A SOCKS4[A] proxy server. 119 | Socks4, 120 | 121 | /// A SOCKS5 proxy server. 122 | Socks5 123 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Models/HttpCompression.cs: -------------------------------------------------------------------------------- 1 | namespace Titanium.Web.Proxy.Compression; 2 | 3 | internal enum HttpCompression 4 | { 5 | Unsupported, 6 | Gzip, 7 | Deflate, 8 | Brotli 9 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Models/IExternalProxy.cs: -------------------------------------------------------------------------------- 1 | namespace Titanium.Web.Proxy.Models; 2 | 3 | public interface IExternalProxy 4 | { 5 | /// 6 | /// Use default windows credentials? 7 | /// 8 | bool UseDefaultCredentials { get; set; } 9 | 10 | /// 11 | /// Bypass this proxy for connections to localhost? 12 | /// 13 | bool BypassLocalhost { get; set; } 14 | 15 | ExternalProxyType ProxyType { get; set; } 16 | 17 | bool ProxyDnsRequests { get; set; } 18 | 19 | /// 20 | /// Username. 21 | /// 22 | string? UserName { get; set; } 23 | 24 | /// 25 | /// Password. 26 | /// 27 | string? Password { get; set; } 28 | 29 | /// 30 | /// Host name. 31 | /// 32 | string HostName { get; set; } 33 | 34 | /// 35 | /// Port. 36 | /// 37 | int Port { get; set; } 38 | 39 | string ToString(); 40 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Models/KnownMethod.cs: -------------------------------------------------------------------------------- 1 | namespace Titanium.Web.Proxy.Helpers; 2 | 3 | internal enum KnownMethod 4 | { 5 | Unknown, 6 | Invalid, 7 | 8 | // RFC 7231: Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content 9 | Connect, 10 | Delete, 11 | Get, 12 | Head, 13 | Options, 14 | Post, 15 | Put, 16 | Trace, 17 | 18 | // RFC 7540: Hypertext Transfer Protocol Version 2 19 | Pri, 20 | 21 | // RFC 5789: PATCH Method for HTTP 22 | Patch, 23 | 24 | // RFC 3744: Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol 25 | Acl, 26 | 27 | // RFC 3253: Versioning Extensions to WebDAV (Web Distributed Authoring and Versioning) 28 | BaselineControl, 29 | Checkin, 30 | Checkout, 31 | Label, 32 | Merge, 33 | Mkactivity, 34 | Mkworkspace, 35 | Report, 36 | Unckeckout, 37 | Update, 38 | VersionControl, 39 | 40 | // RFC 3648: Web Distributed Authoring and Versioning (WebDAV) Ordered Collections Protocol 41 | Orderpatch, 42 | 43 | // RFC 4437: Web Distributed Authoring and Versioning (WebDAV): Redirect Reference Resources 44 | Mkredirectref, 45 | Updateredirectref, 46 | 47 | // RFC 4791: Calendaring Extensions to WebDAV (CalDAV) 48 | Mkcalendar, 49 | 50 | // RFC 4918: HTTP Extensions for Web Distributed Authoring and Versioning (WebDAV) 51 | Copy, 52 | Lock, 53 | Mkcol, 54 | Move, 55 | Propfind, 56 | Proppatch, 57 | Unlock, 58 | 59 | // RFC 5323: Web Distributed Authoring and Versioning (WebDAV) SEARCH 60 | Search, 61 | 62 | // RFC 5842: Binding Extensions to Web Distributed Authoring and Versioning (WebDAV) 63 | Bind, 64 | Rebind, 65 | Unbind, 66 | 67 | // Internet Draft snell-link-method: HTTP Link and Unlink Methods 68 | Link, 69 | Unlink 70 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Models/ProxyAuthenticationContext.cs: -------------------------------------------------------------------------------- 1 | namespace Titanium.Web.Proxy.Models; 2 | 3 | public enum ProxyAuthenticationResult 4 | { 5 | /// 6 | /// Indicates the authentication request was successful 7 | /// 8 | Success, 9 | 10 | /// 11 | /// Indicates the authentication request failed 12 | /// 13 | Failure, 14 | 15 | /// 16 | /// Indicates that this stage of the authentication request succeeded 17 | /// And a second pass of the handshake needs to occur 18 | /// 19 | ContinuationNeeded 20 | } 21 | 22 | /// 23 | /// A context container for authentication flows 24 | /// 25 | public class ProxyAuthenticationContext 26 | { 27 | /// 28 | /// The result of the current authentication request 29 | /// 30 | public ProxyAuthenticationResult Result { get; set; } 31 | 32 | /// 33 | /// An optional continuation token to return to the caller if set 34 | /// 35 | public string? Continuation { get; set; } 36 | 37 | public static ProxyAuthenticationContext Failed() 38 | { 39 | return new ProxyAuthenticationContext 40 | { 41 | Result = ProxyAuthenticationResult.Failure, 42 | Continuation = null 43 | }; 44 | } 45 | 46 | public static ProxyAuthenticationContext Succeeded() 47 | { 48 | return new ProxyAuthenticationContext 49 | { 50 | Result = ProxyAuthenticationResult.Success, 51 | Continuation = null 52 | }; 53 | } 54 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Models/ProxyEndPoint.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Net.Sockets; 3 | using System.Security.Cryptography.X509Certificates; 4 | 5 | namespace Titanium.Web.Proxy.Models; 6 | 7 | /// 8 | /// An abstract endpoint where the proxy listens 9 | /// 10 | public abstract class ProxyEndPoint 11 | { 12 | /// 13 | /// Constructor. 14 | /// 15 | /// 16 | /// 17 | /// 18 | protected ProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl) 19 | { 20 | IpAddress = ipAddress; 21 | Port = port; 22 | DecryptSsl = decryptSsl; 23 | } 24 | 25 | /// 26 | /// underlying TCP Listener object 27 | /// 28 | internal TcpListener? Listener { get; set; } 29 | 30 | /// 31 | /// Ip Address we are listening. 32 | /// 33 | public IPAddress IpAddress { get; } 34 | 35 | /// 36 | /// Port we are listening. 37 | /// 38 | public int Port { get; internal set; } 39 | 40 | /// 41 | /// Enable SSL? 42 | /// 43 | public bool DecryptSsl { get; } 44 | 45 | /// 46 | /// Generic certificate to use for SSL decryption. 47 | /// 48 | public X509Certificate2? GenericCertificate { get; set; } 49 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Models/ProxyProtocolType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Titanium.Web.Proxy.Models; 4 | 5 | [Flags] 6 | public enum ProxyProtocolType 7 | { 8 | /// 9 | /// The none 10 | /// 11 | None = 0, 12 | 13 | /// 14 | /// HTTP 15 | /// 16 | Http = 1, 17 | 18 | /// 19 | /// HTTPS 20 | /// 21 | Https = 2, 22 | 23 | /// 24 | /// Both HTTP and HTTPS 25 | /// 26 | AllHttp = Http | Https 27 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Models/RequestStatusInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Titanium.Web.Proxy.Models; 3 | 4 | namespace Titanium.Web.Proxy.Helpers; 5 | 6 | internal struct RequestStatusInfo 7 | { 8 | public string Method { get; set; } 9 | 10 | public ByteString RequestUri { get; set; } 11 | 12 | public Version Version { get; set; } 13 | 14 | public bool IsEmpty() 15 | { 16 | return Method == null && RequestUri.Length == 0 && Version == null; 17 | } 18 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Models/ResponseStatusInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Titanium.Web.Proxy.Helpers; 4 | 5 | internal struct ResponseStatusInfo 6 | { 7 | public Version Version { get; set; } 8 | 9 | public int StatusCode { get; set; } 10 | 11 | public string Description { get; set; } 12 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Models/SocksProxyEndPoint.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Net; 3 | using System.Threading.Tasks; 4 | using Titanium.Web.Proxy.EventArguments; 5 | using Titanium.Web.Proxy.Extensions; 6 | 7 | namespace Titanium.Web.Proxy.Models; 8 | 9 | /// 10 | /// A proxy end point client is not aware of. 11 | /// Useful when requests are redirected to this proxy end point through port forwarding via router. 12 | /// 13 | [DebuggerDisplay("SOCKS: {IpAddress}:{Port}")] 14 | public class SocksProxyEndPoint : TransparentBaseProxyEndPoint 15 | { 16 | /// 17 | /// Initialize a new instance. 18 | /// 19 | /// Listening Ip address. 20 | /// Listening port. 21 | /// Should we decrypt ssl? 22 | public SocksProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl = true) : base(ipAddress, port, 23 | decryptSsl) 24 | { 25 | GenericCertificateName = "localhost"; 26 | } 27 | 28 | /// 29 | /// Name of the Certificate need to be sent (same as the hostname we want to proxy). 30 | /// This is valid only when UseServerNameIndication is set to false. 31 | /// 32 | public override string GenericCertificateName { get; set; } 33 | 34 | /// 35 | /// Before Ssl authentication this event is fired. 36 | /// 37 | public event AsyncEventHandler? BeforeSslAuthenticate; 38 | 39 | internal override async Task InvokeBeforeSslAuthenticate(ProxyServer proxyServer, 40 | BeforeSslAuthenticateEventArgs connectArgs, ExceptionHandler? exceptionFunc) 41 | { 42 | if (BeforeSslAuthenticate != null) 43 | await BeforeSslAuthenticate.InvokeAsync(proxyServer, connectArgs, exceptionFunc); 44 | } 45 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Models/TransparentBaseProxyEndPoint.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using Titanium.Web.Proxy.EventArguments; 4 | 5 | namespace Titanium.Web.Proxy.Models; 6 | 7 | public abstract class TransparentBaseProxyEndPoint : ProxyEndPoint 8 | { 9 | protected TransparentBaseProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl) : base(ipAddress, port, 10 | decryptSsl) 11 | { 12 | } 13 | 14 | /// 15 | /// The hostname of the generic certificate to negotiate SSL. 16 | /// This will be only used when Sever Name Indication (SNI) is not supported by client, 17 | /// or when it does not indicate any host name. 18 | /// 19 | public abstract string GenericCertificateName { get; set; } 20 | 21 | internal abstract Task InvokeBeforeSslAuthenticate(ProxyServer proxyServer, 22 | BeforeSslAuthenticateEventArgs connectArgs, ExceptionHandler? exceptionFunc); 23 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Models/TransparentProxyEndPoint.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Net; 3 | using System.Threading.Tasks; 4 | using Titanium.Web.Proxy.EventArguments; 5 | using Titanium.Web.Proxy.Extensions; 6 | 7 | namespace Titanium.Web.Proxy.Models; 8 | 9 | /// 10 | /// A proxy end point client is not aware of. 11 | /// Useful when requests are redirected to this proxy end point through port forwarding via router. 12 | /// 13 | [DebuggerDisplay("Transparent: {IpAddress}:{Port}")] 14 | public class TransparentProxyEndPoint : TransparentBaseProxyEndPoint 15 | { 16 | /// 17 | /// Initialize a new instance. 18 | /// 19 | /// Listening Ip address. 20 | /// Listening port. 21 | /// Should we decrypt ssl? 22 | public TransparentProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl = true) : base(ipAddress, port, 23 | decryptSsl) 24 | { 25 | GenericCertificateName = "localhost"; 26 | } 27 | 28 | /// 29 | /// Name of the Certificate need to be sent (same as the hostname we want to proxy). 30 | /// This is valid only when UseServerNameIndication is set to false. 31 | /// 32 | public override string GenericCertificateName { get; set; } 33 | 34 | /// 35 | /// Before Ssl authentication this event is fired. 36 | /// 37 | public event AsyncEventHandler? BeforeSslAuthenticate; 38 | 39 | internal override async Task InvokeBeforeSslAuthenticate(ProxyServer proxyServer, 40 | BeforeSslAuthenticateEventArgs connectArgs, ExceptionHandler? exceptionFunc) 41 | { 42 | if (BeforeSslAuthenticate != null) 43 | await BeforeSslAuthenticate.InvokeAsync(proxyServer, connectArgs, exceptionFunc); 44 | } 45 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Network/BufferPool/DefaultBufferPool.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | 3 | namespace Titanium.Web.Proxy.StreamExtended.BufferPool; 4 | 5 | /// 6 | /// A concrete IBufferPool implementation using a thread-safe stack. 7 | /// Works well when all consumers ask for buffers with the same size. 8 | /// If your application would use variable size buffers consider implementing IBufferPool using System.Buffers library 9 | /// from Microsoft. 10 | /// 11 | internal class DefaultBufferPool : IBufferPool 12 | { 13 | /// 14 | /// Buffer size in bytes used throughout this proxy. 15 | /// Default value is 8192 bytes. 16 | /// 17 | public int BufferSize { get; set; } = 8192; 18 | 19 | /// 20 | /// Gets a buffer with a default size. 21 | /// 22 | /// 23 | public byte[] GetBuffer() 24 | { 25 | return ArrayPool.Shared.Rent(BufferSize); 26 | } 27 | 28 | /// 29 | /// Gets a buffer. 30 | /// 31 | /// Size of the buffer. 32 | /// 33 | public byte[] GetBuffer(int bufferSize) 34 | { 35 | return ArrayPool.Shared.Rent(bufferSize); 36 | } 37 | 38 | /// 39 | /// Returns the buffer. 40 | /// 41 | /// The buffer. 42 | public void ReturnBuffer(byte[] buffer) 43 | { 44 | ArrayPool.Shared.Return(buffer); 45 | } 46 | 47 | public void Dispose() 48 | { 49 | //Nothing to dispose. But need for the interface 50 | } 51 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Network/BufferPool/IBufferPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Titanium.Web.Proxy.StreamExtended.BufferPool; 4 | 5 | /// 6 | /// Use this interface to implement custom buffer pool. 7 | /// To use the default buffer pool implementation use DefaultBufferPool class. 8 | /// 9 | public interface IBufferPool : IDisposable 10 | { 11 | int BufferSize { get; } 12 | 13 | byte[] GetBuffer(); 14 | 15 | byte[] GetBuffer(int bufferSize); 16 | 17 | void ReturnBuffer(byte[] buffer); 18 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Network/Models/TaskResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Titanium.Web.Proxy.StreamExtended.Network; 6 | 7 | /// 8 | /// Mimic a Task but you can set AsyncState 9 | /// 10 | public class TaskResult : IAsyncResult 11 | { 12 | private readonly Task task; 13 | 14 | public TaskResult(Task pTask, object state) 15 | { 16 | task = pTask; 17 | AsyncState = state; 18 | } 19 | 20 | public object AsyncState { get; } 21 | 22 | public WaitHandle AsyncWaitHandle => ((IAsyncResult)task).AsyncWaitHandle; 23 | 24 | public bool CompletedSynchronously => ((IAsyncResult)task).CompletedSynchronously; 25 | 26 | public bool IsCompleted => task.IsCompleted; 27 | 28 | public void GetResult() 29 | { 30 | task.GetAwaiter().GetResult(); 31 | } 32 | } 33 | 34 | /// 35 | /// Mimic a Task<T> but you can set AsyncState 36 | /// 37 | /// 38 | public class TaskResult : IAsyncResult 39 | { 40 | private readonly Task task; 41 | 42 | public TaskResult(Task pTask, object state) 43 | { 44 | task = pTask; 45 | AsyncState = state; 46 | } 47 | 48 | public T Result => task.Result; 49 | 50 | public object AsyncState { get; } 51 | 52 | public WaitHandle AsyncWaitHandle => ((IAsyncResult)task).AsyncWaitHandle; 53 | 54 | public bool CompletedSynchronously => ((IAsyncResult)task).CompletedSynchronously; 55 | 56 | public bool IsCompleted => task.IsCompleted; 57 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Network/Readers/IHttpStreamReader.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Titanium.Web.Proxy.EventArguments; 4 | 5 | namespace Titanium.Web.Proxy.StreamExtended.Network; 6 | 7 | public interface IHttpStreamReader : ILineStream 8 | { 9 | int Read(byte[] buffer, int offset, int count); 10 | 11 | Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken); 12 | 13 | Task CopyBodyAsync(IHttpStreamWriter writer, bool isChunked, long contentLength, 14 | bool isRequest, SessionEventArgs args, CancellationToken cancellationToken); 15 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Network/Readers/PeekStreamReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Titanium.Web.Proxy.StreamExtended.Network; 6 | 7 | internal class PeekStreamReader 8 | { 9 | private readonly IPeekStream baseStream; 10 | 11 | public PeekStreamReader(IPeekStream baseStream, int startPosition = 0) 12 | { 13 | this.baseStream = baseStream; 14 | Position = startPosition; 15 | } 16 | 17 | public int Position { get; private set; } 18 | 19 | public async ValueTask EnsureBufferLength(int length, CancellationToken cancellationToken) 20 | { 21 | var val = await baseStream.PeekByteAsync(Position + length - 1, cancellationToken); 22 | return val != -1; 23 | } 24 | 25 | public byte ReadByte() 26 | { 27 | return baseStream.PeekByteFromBuffer(Position++); 28 | } 29 | 30 | public int ReadInt16() 31 | { 32 | int i1 = ReadByte(); 33 | int i2 = ReadByte(); 34 | return (i1 << 8) + i2; 35 | } 36 | 37 | public int ReadInt24() 38 | { 39 | int i1 = ReadByte(); 40 | int i2 = ReadByte(); 41 | int i3 = ReadByte(); 42 | return (i1 << 16) + (i2 << 8) + i3; 43 | } 44 | 45 | public byte[] ReadBytes(int length) 46 | { 47 | var buffer = new byte[length]; 48 | for (var i = 0; i < buffer.Length; i++) buffer[i] = ReadByte(); 49 | 50 | return buffer; 51 | } 52 | 53 | public void ReadBytes(Span data) 54 | { 55 | for (var i = 0; i < data.Length; i++) data[i] = ReadByte(); 56 | } 57 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Network/RetryPolicy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Titanium.Web.Proxy.Network.Tcp; 4 | 5 | namespace Titanium.Web.Proxy.Network; 6 | 7 | internal class RetryPolicy where T : Exception 8 | { 9 | private readonly int retries; 10 | private readonly TcpConnectionFactory tcpConnectionFactory; 11 | 12 | private TcpServerConnection? currentConnection; 13 | 14 | internal RetryPolicy(int retries, TcpConnectionFactory tcpConnectionFactory) 15 | { 16 | this.retries = retries; 17 | this.tcpConnectionFactory = tcpConnectionFactory; 18 | } 19 | 20 | /// 21 | /// Execute and retry the given action until retry number of times. 22 | /// 23 | /// The action to retry with return value specifying whether caller should continue execution. 24 | /// The Tcp connection generator to be invoked to get new connection for retry. 25 | /// Initial Tcp connection to use. 26 | /// Returns the latest connection used and the latest exception if any. 27 | internal async Task ExecuteAsync(Func> action, 28 | Func> generator, TcpServerConnection? initialConnection) 29 | { 30 | currentConnection = initialConnection; 31 | var @continue = true; 32 | Exception? exception = null; 33 | 34 | var attempts = retries; 35 | 36 | while (true) 37 | { 38 | // setup connection 39 | currentConnection ??= await generator(); 40 | 41 | try 42 | { 43 | @continue = await action(currentConnection); 44 | } 45 | catch (Exception ex) 46 | { 47 | exception = ex; 48 | } 49 | 50 | attempts--; 51 | 52 | if (attempts < 0 || exception == null || !(exception is T)) break; 53 | 54 | exception = null; 55 | 56 | // before retry clear connection 57 | await tcpConnectionFactory.Release(currentConnection, true); 58 | currentConnection = null; 59 | } 60 | 61 | return new RetryResult(currentConnection, exception, @continue); 62 | } 63 | } 64 | 65 | internal class RetryResult 66 | { 67 | internal RetryResult(TcpServerConnection? lastConnection, Exception? exception, bool @continue) 68 | { 69 | LatestConnection = lastConnection; 70 | Exception = exception; 71 | Continue = @continue; 72 | } 73 | 74 | internal TcpServerConnection? LatestConnection { get; } 75 | 76 | internal Exception? Exception { get; } 77 | 78 | internal bool Continue { get; } 79 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Network/Streams/CopyStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Titanium.Web.Proxy.Helpers; 5 | using Titanium.Web.Proxy.StreamExtended.BufferPool; 6 | 7 | namespace Titanium.Web.Proxy.StreamExtended.Network; 8 | 9 | /// 10 | /// Copies the source stream to destination stream. 11 | /// But this let users to peek and read the copying process. 12 | /// 13 | internal class CopyStream : ILineStream, IDisposable 14 | { 15 | private readonly byte[] buffer; 16 | 17 | private readonly IBufferPool bufferPool; 18 | private readonly IHttpStreamReader reader; 19 | 20 | private readonly IHttpStreamWriter writer; 21 | 22 | private int bufferLength; 23 | 24 | private bool disposed; 25 | 26 | public CopyStream(IHttpStreamReader reader, IHttpStreamWriter writer, IBufferPool bufferPool) 27 | { 28 | this.reader = reader; 29 | this.writer = writer; 30 | buffer = bufferPool.GetBuffer(); 31 | this.bufferPool = bufferPool; 32 | } 33 | 34 | public long ReadBytes { get; private set; } 35 | 36 | public void Dispose() 37 | { 38 | Dispose(true); 39 | GC.SuppressFinalize(this); 40 | } 41 | 42 | public bool DataAvailable => reader.DataAvailable; 43 | 44 | public async ValueTask FillBufferAsync(CancellationToken cancellationToken = default) 45 | { 46 | await FlushAsync(cancellationToken); 47 | return await reader.FillBufferAsync(cancellationToken); 48 | } 49 | 50 | public byte ReadByteFromBuffer() 51 | { 52 | var b = reader.ReadByteFromBuffer(); 53 | buffer[bufferLength++] = b; 54 | ReadBytes++; 55 | return b; 56 | } 57 | 58 | public ValueTask ReadLineAsync(CancellationToken cancellationToken = default) 59 | { 60 | return HttpStream.ReadLineInternalAsync(this, bufferPool, cancellationToken); 61 | } 62 | 63 | public async Task FlushAsync(CancellationToken cancellationToken = default) 64 | { 65 | // send out the current data from from the buffer 66 | if (bufferLength > 0) 67 | { 68 | await writer.WriteAsync(buffer, 0, bufferLength, cancellationToken); 69 | bufferLength = 0; 70 | } 71 | } 72 | 73 | protected virtual void Dispose(bool disposing) 74 | { 75 | if (disposed) return; 76 | 77 | if (disposing) bufferPool.ReturnBuffer(buffer); 78 | 79 | disposed = true; 80 | } 81 | 82 | ~CopyStream() 83 | { 84 | #if DEBUG 85 | // Finalizer should not be called 86 | System.Diagnostics.Debugger.Break(); 87 | #endif 88 | 89 | Dispose(false); 90 | } 91 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Network/Streams/HttpClientStream.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Titanium.Web.Proxy.Http; 5 | using Titanium.Web.Proxy.Network.Tcp; 6 | using Titanium.Web.Proxy.StreamExtended.BufferPool; 7 | 8 | namespace Titanium.Web.Proxy.Helpers; 9 | 10 | internal sealed class HttpClientStream : HttpStream 11 | { 12 | internal HttpClientStream(ProxyServer server, TcpClientConnection connection, Stream stream, IBufferPool bufferPool, 13 | CancellationToken cancellationToken) 14 | : base(server, stream, bufferPool, cancellationToken) 15 | { 16 | Connection = connection; 17 | } 18 | 19 | public TcpClientConnection Connection { get; } 20 | 21 | /// 22 | /// Writes the response. 23 | /// 24 | /// The response object. 25 | /// Optional cancellation token for this async task. 26 | /// The Task. 27 | internal async ValueTask WriteResponseAsync(Response response, CancellationToken cancellationToken = default) 28 | { 29 | var headerBuilder = new HeaderBuilder(); 30 | 31 | // Write back response status to client 32 | headerBuilder.WriteResponseLine(response.HttpVersion, response.StatusCode, response.StatusDescription); 33 | 34 | await WriteAsync(response, headerBuilder, cancellationToken); 35 | } 36 | 37 | internal async ValueTask ReadRequestLine(CancellationToken cancellationToken = default) 38 | { 39 | // read the first line HTTP command 40 | var httpCmd = await ReadLineAsync(cancellationToken); 41 | if (string.IsNullOrEmpty(httpCmd)) return default; 42 | 43 | Request.ParseRequestLine(httpCmd!, out var method, out var requestUri, out var version); 44 | 45 | return new RequestStatusInfo { Method = method, RequestUri = requestUri, Version = version }; 46 | } 47 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Network/Streams/HttpServerStream.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Titanium.Web.Proxy.Http; 5 | using Titanium.Web.Proxy.StreamExtended.BufferPool; 6 | 7 | namespace Titanium.Web.Proxy.Helpers; 8 | 9 | internal sealed class HttpServerStream : HttpStream 10 | { 11 | internal HttpServerStream(ProxyServer server, Stream stream, IBufferPool bufferPool, 12 | CancellationToken cancellationToken) 13 | : base(server, stream, bufferPool, cancellationToken) 14 | { 15 | } 16 | 17 | /// 18 | /// Writes the request. 19 | /// 20 | /// The request object. 21 | /// Optional cancellation token for this async task. 22 | /// 23 | internal async ValueTask WriteRequestAsync(Request request, CancellationToken cancellationToken = default) 24 | { 25 | var headerBuilder = new HeaderBuilder(); 26 | headerBuilder.WriteRequestLine(request.Method, request.RequestUriString, request.HttpVersion); 27 | await WriteAsync(request, headerBuilder, cancellationToken); 28 | } 29 | 30 | internal async ValueTask ReadResponseStatus(CancellationToken cancellationToken = default) 31 | { 32 | var httpStatus = await ReadLineAsync(cancellationToken) ?? 33 | throw new IOException("Invalid http status code."); 34 | 35 | if (httpStatus == string.Empty) 36 | // is this really possible? 37 | httpStatus = await ReadLineAsync(cancellationToken) ?? 38 | throw new IOException("Response status is empty."); 39 | 40 | Response.ParseResponseLine(httpStatus, out var version, out var statusCode, out var description); 41 | return new ResponseStatusInfo { Version = version, StatusCode = statusCode, Description = description }; 42 | } 43 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Network/Streams/ILineStream.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Titanium.Web.Proxy.StreamExtended.Network; 5 | 6 | public interface ILineStream 7 | { 8 | bool DataAvailable { get; } 9 | 10 | /// 11 | /// Fills the buffer asynchronous. 12 | /// 13 | /// 14 | ValueTask FillBufferAsync(CancellationToken cancellationToken = default); 15 | 16 | byte ReadByteFromBuffer(); 17 | 18 | /// 19 | /// Read a line from the byte stream 20 | /// 21 | /// 22 | ValueTask ReadLineAsync(CancellationToken cancellationToken = default); 23 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Network/Streams/IPeekStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Titanium.Web.Proxy.StreamExtended.Network; 6 | 7 | public interface IPeekStream 8 | { 9 | /// 10 | /// Peeks a byte from buffer. 11 | /// 12 | /// The index. 13 | /// 14 | /// Index is out of buffer size 15 | byte PeekByteFromBuffer(int index); 16 | 17 | /// 18 | /// Peeks a byte asynchronous. 19 | /// 20 | /// The index. 21 | /// The cancellation token. 22 | /// 23 | ValueTask PeekByteAsync(int index, CancellationToken cancellationToken = default); 24 | 25 | /// 26 | /// Peeks bytes asynchronous. 27 | /// 28 | /// The buffer to copy. 29 | /// The offset where copying. 30 | /// The index. 31 | /// The count. 32 | /// The cancellation token. 33 | /// 34 | ValueTask PeekBytesAsync(byte[] buffer, int offset, int index, int count, 35 | CancellationToken cancellationToken = default); 36 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Network/TcpConnection/TcpClientConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Net.Security; 5 | using System.Net.Sockets; 6 | using System.Security.Authentication; 7 | using System.Threading.Tasks; 8 | using Titanium.Web.Proxy.Helpers; 9 | using Titanium.Web.Proxy.Models; 10 | 11 | namespace Titanium.Web.Proxy.Network.Tcp; 12 | 13 | /// 14 | /// An object that holds TcpConnection to a particular server and port 15 | /// 16 | internal class TcpClientConnection : IDisposable 17 | { 18 | private readonly Socket tcpClientSocket; 19 | 20 | private bool disposed; 21 | 22 | private int? processId; 23 | 24 | internal TcpClientConnection(ProxyServer proxyServer, Socket tcpClientSocket) 25 | { 26 | this.tcpClientSocket = tcpClientSocket; 27 | ProxyServer = proxyServer; 28 | ProxyServer.UpdateClientConnectionCount(true); 29 | } 30 | 31 | public object ClientUserData { get; set; } 32 | 33 | private ProxyServer ProxyServer { get; } 34 | 35 | public Guid Id { get; } = Guid.NewGuid(); 36 | 37 | public EndPoint LocalEndPoint => tcpClientSocket.LocalEndPoint; 38 | 39 | public EndPoint RemoteEndPoint => tcpClientSocket.RemoteEndPoint; 40 | 41 | internal SslProtocols SslProtocol { get; set; } 42 | 43 | internal SslApplicationProtocol NegotiatedApplicationProtocol { get; set; } 44 | 45 | public void Dispose() 46 | { 47 | Dispose(true); 48 | GC.SuppressFinalize(this); 49 | } 50 | 51 | public Stream GetStream() 52 | { 53 | return new NetworkStream(tcpClientSocket, true); 54 | } 55 | 56 | public int GetProcessId(ProxyEndPoint endPoint) 57 | { 58 | if (processId.HasValue) return processId.Value; 59 | 60 | if (RunTime.IsWindows) 61 | { 62 | var remoteEndPoint = (IPEndPoint)RemoteEndPoint; 63 | 64 | // If client is localhost get the process id 65 | if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address)) 66 | processId = TcpHelper.GetProcessIdByLocalPort(endPoint.IpAddress.AddressFamily, remoteEndPoint.Port); 67 | else 68 | // can't access process Id of remote request from remote machine 69 | processId = -1; 70 | 71 | return processId.Value; 72 | } 73 | 74 | throw new PlatformNotSupportedException(); 75 | } 76 | 77 | protected virtual void Dispose(bool disposing) 78 | { 79 | if (disposed) return; 80 | 81 | Task.Run(async () => 82 | { 83 | // delay calling tcp connection close() 84 | // so that client have enough time to call close first. 85 | // This way we can push tcp Time_Wait to client side when possible. 86 | await Task.Delay(1000); 87 | ProxyServer.UpdateClientConnectionCount(false); 88 | 89 | if (disposing) 90 | try 91 | { 92 | tcpClientSocket.Close(); 93 | } 94 | catch 95 | { 96 | // ignore 97 | } 98 | }); 99 | 100 | disposed = true; 101 | } 102 | 103 | ~TcpClientConnection() 104 | { 105 | #if DEBUG 106 | // Finalizer should not be called 107 | System.Diagnostics.Debugger.Break(); 108 | #endif 109 | 110 | Dispose(false); 111 | } 112 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Network/TcpConnection/TcpServerConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Security; 4 | using System.Net.Sockets; 5 | using System.Threading.Tasks; 6 | using Titanium.Web.Proxy.Helpers; 7 | using Titanium.Web.Proxy.Models; 8 | 9 | namespace Titanium.Web.Proxy.Network.Tcp; 10 | 11 | /// 12 | /// An object that holds TcpConnection to a particular server and port 13 | /// 14 | internal class TcpServerConnection : IDisposable 15 | { 16 | private bool disposed; 17 | 18 | internal TcpServerConnection(ProxyServer proxyServer, Socket tcpSocket, HttpServerStream stream, 19 | string hostName, int port, bool isHttps, SslApplicationProtocol negotiatedApplicationProtocol, 20 | Version version, IExternalProxy? upStreamProxy, IPEndPoint? upStreamEndPoint, string cacheKey) 21 | { 22 | TcpSocket = tcpSocket; 23 | LastAccess = DateTime.UtcNow; 24 | ProxyServer = proxyServer; 25 | ProxyServer.UpdateServerConnectionCount(true); 26 | Stream = stream; 27 | HostName = hostName; 28 | Port = port; 29 | IsHttps = isHttps; 30 | NegotiatedApplicationProtocol = negotiatedApplicationProtocol; 31 | Version = version; 32 | UpStreamProxy = upStreamProxy; 33 | UpStreamEndPoint = upStreamEndPoint; 34 | 35 | CacheKey = cacheKey; 36 | } 37 | 38 | public Guid Id { get; } = Guid.NewGuid(); 39 | 40 | private ProxyServer ProxyServer { get; } 41 | 42 | internal bool IsClosed => Stream.IsClosed; 43 | 44 | internal IExternalProxy? UpStreamProxy { get; set; } 45 | 46 | internal string HostName { get; set; } 47 | 48 | internal int Port { get; set; } 49 | 50 | internal bool IsHttps { get; set; } 51 | 52 | internal SslApplicationProtocol NegotiatedApplicationProtocol { get; set; } 53 | 54 | /// 55 | /// Local NIC via connection is made 56 | /// 57 | internal IPEndPoint? UpStreamEndPoint { get; set; } 58 | 59 | /// 60 | /// Http version 61 | /// 62 | internal Version Version { get; set; } = HttpHeader.VersionUnknown; 63 | 64 | /// 65 | /// The TcpClient. 66 | /// 67 | internal Socket TcpSocket { get; } 68 | 69 | /// 70 | /// Used to write lines to server 71 | /// 72 | internal HttpServerStream Stream { get; } 73 | 74 | /// 75 | /// Last time this connection was used 76 | /// 77 | internal DateTime LastAccess { get; set; } 78 | 79 | /// 80 | /// The cache key used to uniquely identify this connection properties 81 | /// 82 | internal string CacheKey { get; set; } 83 | 84 | /// 85 | /// Is this connection authenticated via WinAuth 86 | /// 87 | internal bool IsWinAuthenticated { get; set; } 88 | 89 | public void Dispose() 90 | { 91 | Dispose(true); 92 | GC.SuppressFinalize(this); 93 | } 94 | 95 | protected virtual void Dispose(bool disposing) 96 | { 97 | if (disposed) return; 98 | 99 | Task.Run(async () => 100 | { 101 | // delay calling tcp connection close() 102 | // so that server have enough time to call close first. 103 | // This way we can push tcp Time_Wait to server side when possible. 104 | await Task.Delay(1000); 105 | 106 | ProxyServer.UpdateServerConnectionCount(false); 107 | 108 | if (disposing) 109 | { 110 | Stream.Dispose(); 111 | 112 | try 113 | { 114 | TcpSocket.Close(); 115 | } 116 | catch 117 | { 118 | // ignore 119 | } 120 | } 121 | }); 122 | 123 | disposed = true; 124 | } 125 | 126 | ~TcpServerConnection() 127 | { 128 | #if DEBUG 129 | // Finalizer should not be called 130 | System.Diagnostics.Debugger.Break(); 131 | #endif 132 | 133 | Dispose(false); 134 | } 135 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Network/WinAuth/Security/State.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Titanium.Web.Proxy.Network.WinAuth.Security; 4 | 5 | /// 6 | /// Status of authenticated session 7 | /// 8 | internal class State 9 | { 10 | /// 11 | /// States during Windows Authentication 12 | /// 13 | public enum WinAuthState 14 | { 15 | Unauthorized, 16 | InitialToken, 17 | FinalToken, 18 | Authorized 19 | } 20 | 21 | /// 22 | /// Current state of the authentication process 23 | /// 24 | internal WinAuthState AuthState; 25 | 26 | /// 27 | /// Context will be used to validate HTLM hashes 28 | /// 29 | internal Common.SecurityHandle Context; 30 | 31 | /// 32 | /// Credentials used to validate NTLM hashes 33 | /// 34 | internal Common.SecurityHandle Credentials; 35 | 36 | /// 37 | /// Timestamp needed to calculate validity of the authenticated session 38 | /// 39 | internal DateTime LastSeen; 40 | 41 | internal State() 42 | { 43 | Credentials = new Common.SecurityHandle(0); 44 | Context = new Common.SecurityHandle(0); 45 | 46 | LastSeen = DateTime.UtcNow; 47 | AuthState = WinAuthState.Unauthorized; 48 | } 49 | 50 | internal void ResetHandles() 51 | { 52 | Credentials.Reset(); 53 | Context.Reset(); 54 | AuthState = WinAuthState.Unauthorized; 55 | } 56 | 57 | internal void UpdatePresence() 58 | { 59 | LastSeen = DateTime.UtcNow; 60 | } 61 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Network/WinAuth/WinAuthHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Titanium.Web.Proxy.Http; 3 | using Titanium.Web.Proxy.Network.WinAuth.Security; 4 | 5 | namespace Titanium.Web.Proxy.Network.WinAuth; 6 | 7 | /// 8 | /// A handler for NTLM/Kerberos windows authentication challenge from server 9 | /// NTLM process details below 10 | /// https://blogs.msdn.microsoft.com/chiranth/2013/09/20/ntlm-want-to-know-how-it-works/ 11 | /// 12 | internal static class WinAuthHandler 13 | { 14 | /// 15 | /// Get the initial client token for server 16 | /// using credentials of user running the proxy server process 17 | /// 18 | /// 19 | /// 20 | /// 21 | /// 22 | internal static string GetInitialAuthToken(string serverHostname, string authScheme, InternalDataStore data) 23 | { 24 | var tokenBytes = WinAuthEndPoint.AcquireInitialSecurityToken(serverHostname, authScheme, data); 25 | return string.Concat(" ", Convert.ToBase64String(tokenBytes)); 26 | } 27 | 28 | /// 29 | /// Get the final token given the server challenge token 30 | /// 31 | /// 32 | /// 33 | /// 34 | /// 35 | internal static string GetFinalAuthToken(string serverHostname, string serverToken, InternalDataStore data) 36 | { 37 | var tokenBytes = 38 | WinAuthEndPoint.AcquireFinalSecurityToken(serverHostname, Convert.FromBase64String(serverToken), 39 | data); 40 | 41 | return string.Concat(" ", Convert.ToBase64String(tokenBytes)); 42 | } 43 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Network/Writers/IHttpStreamWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Titanium.Web.Proxy.StreamExtended.Network; 5 | 6 | /// 7 | /// A concrete implementation of this interface is required when calling CopyStream. 8 | /// 9 | public interface IHttpStreamWriter 10 | { 11 | bool IsNetworkStream { get; } 12 | 13 | void Write(byte[] buffer, int offset, int count); 14 | 15 | Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken); 16 | 17 | ValueTask WriteLineAsync(CancellationToken cancellationToken = default); 18 | 19 | ValueTask WriteLineAsync(string value, CancellationToken cancellationToken = default); 20 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Network/Writers/NullWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Titanium.Web.Proxy.StreamExtended.Network; 5 | 6 | namespace Titanium.Web.Proxy.Helpers; 7 | 8 | internal class NullWriter : IHttpStreamWriter 9 | { 10 | private NullWriter() 11 | { 12 | } 13 | 14 | public static NullWriter Instance { get; } = new(); 15 | 16 | public bool IsNetworkStream => false; 17 | 18 | public void Write(byte[] buffer, int offset, int count) 19 | { 20 | } 21 | 22 | public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 23 | { 24 | return Task.CompletedTask; 25 | } 26 | 27 | public ValueTask WriteLineAsync(CancellationToken cancellationToken = default) 28 | { 29 | throw new NotImplementedException(); 30 | } 31 | 32 | public ValueTask WriteLineAsync(string value, CancellationToken cancellationToken = default) 33 | { 34 | throw new NotImplementedException(); 35 | } 36 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | 9 | [assembly: AssemblyTitle("Titanium.Web.Proxy")] 10 | [assembly: AssemblyDescription("")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("Titanium.Web.Proxy")] 14 | [assembly: AssemblyCopyright("Copyright © Titanium 2015-2020")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | [assembly: InternalsVisibleTo("Titanium.Web.Proxy.UnitTests, PublicKey=" + 18 | "0024000004800000940000000602000000240000525341310004000001000100e7368e0ccc717e" + 19 | "eb4d57d35ad6a8305cbbed14faa222e13869405e92c83856266d400887d857005f1393ffca2b92" + 20 | "de7f3ba0bdad35ec2d6057ee1846091b34be2abc3f97dc7e72c16fd4958c15126b12923df76964" + 21 | "7d84922c3f4f3b80ee0ae8e4cb40bc1973b782afb90bb00519fd16adf960f217e23696e7c31654" + 22 | "01d0acd6")] 23 | [assembly: InternalsVisibleTo("Titanium.Web.Proxy.IntegrationTests, PublicKey=" + 24 | "0024000004800000940000000602000000240000525341310004000001000100e7368e0ccc717e" + 25 | "eb4d57d35ad6a8305cbbed14faa222e13869405e92c83856266d400887d857005f1393ffca2b92" + 26 | "de7f3ba0bdad35ec2d6057ee1846091b34be2abc3f97dc7e72c16fd4958c15126b12923df76964" + 27 | "7d84922c3f4f3b80ee0ae8e4cb40bc1973b782afb90bb00519fd16adf960f217e23696e7c31654" + 28 | "01d0acd6")] 29 | 30 | // Setting ComVisible to false makes the types in this assembly not visible 31 | // to COM components. If you need to access a type in this assembly from 32 | // COM, set the ComVisible attribute to true on that type. 33 | 34 | [assembly: ComVisible(false)] 35 | 36 | // The following GUID is for the ID of the typelib if this project is exposed to COM 37 | 38 | [assembly: Guid("5036e0b7-a0d0-4070-8eb0-72c129dee9b3")] 39 | 40 | // Version information for an assembly consists of the following four values: 41 | // 42 | // Major Version 43 | // Minor Version 44 | // Build Number 45 | // Revision 46 | // 47 | 48 | [assembly: AssemblyVersion("1.0.1")] 49 | [assembly: AssemblyFileVersion("1.0.1")] -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/ProxySocket/Authentication/AuthNone.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2002, The KPD-Team 3 | All rights reserved. 4 | http://www.mentalis.org/ 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions 8 | are met: 9 | 10 | - Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | - Neither the name of the KPD-Team, nor the names of its contributors 14 | may be used to endorse or promote products derived from this 15 | software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 20 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 21 | THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 22 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 26 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 28 | OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | using System.Net.Sockets; 32 | 33 | namespace Titanium.Web.Proxy.ProxySocket.Authentication; 34 | 35 | /// 36 | /// This class implements the 'No Authentication' scheme. 37 | /// 38 | internal sealed class AuthNone : AuthMethod 39 | { 40 | /// 41 | /// Initializes an AuthNone instance. 42 | /// 43 | /// The socket connection with the proxy server. 44 | public AuthNone(Socket server) : base(server) 45 | { 46 | } 47 | 48 | /// 49 | /// Authenticates the user. 50 | /// 51 | public override void Authenticate() 52 | { 53 | } 54 | 55 | /// 56 | /// Authenticates the user asynchronously. 57 | /// 58 | /// The method to call when the authentication is complete. 59 | /// This method immediately calls the callback method. 60 | public override void BeginAuthenticate(HandShakeComplete callback) 61 | { 62 | callback(null); 63 | } 64 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/ProxySocket/ProxyException.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2002, The KPD-Team 3 | All rights reserved. 4 | http://www.mentalis.org/ 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions 8 | are met: 9 | 10 | - Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | - Neither the name of the KPD-Team, nor the names of its contributors 14 | may be used to endorse or promote products derived from this 15 | software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 20 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 21 | THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 22 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 26 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 28 | OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | using System; 32 | 33 | namespace Titanium.Web.Proxy.ProxySocket; 34 | 35 | /// 36 | /// The exception that is thrown when a proxy error occurs. 37 | /// 38 | [Serializable] 39 | internal class ProxyException : Exception 40 | { 41 | /// 42 | /// Initializes a new instance of the ProxyException class. 43 | /// 44 | public ProxyException() : this("An error occured while talking to the proxy server.") 45 | { 46 | } 47 | 48 | /// 49 | /// Initializes a new instance of the ProxyException class. 50 | /// 51 | /// The message that describes the error. 52 | public ProxyException(string message) : base(message) 53 | { 54 | } 55 | 56 | /// 57 | /// Initializes a new instance of the ProxyException class. 58 | /// 59 | /// The error number returned by a SOCKS5 server. 60 | public ProxyException(int socks5Error) : this(Socks5ToString(socks5Error)) 61 | { 62 | } 63 | 64 | /// 65 | /// Converts a SOCKS5 error number to a human readable string. 66 | /// 67 | /// The error number returned by a SOCKS5 server. 68 | /// A string representation of the specified SOCKS5 error number. 69 | public static string Socks5ToString(int socks5Error) 70 | { 71 | switch (socks5Error) 72 | { 73 | case 0: 74 | return "Connection succeeded."; 75 | case 1: 76 | return "General SOCKS server failure."; 77 | case 2: 78 | return "Connection not allowed by ruleset."; 79 | case 3: 80 | return "Network unreachable."; 81 | case 4: 82 | return "Host unreachable."; 83 | case 5: 84 | return "Connection refused."; 85 | case 6: 86 | return "TTL expired."; 87 | case 7: 88 | return "Command not supported."; 89 | case 8: 90 | return "Address type not supported."; 91 | default: 92 | return "Unspecified SOCKS error."; 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Shared/ProxyConstants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.RegularExpressions; 4 | using Titanium.Web.Proxy.Http; 5 | 6 | namespace Titanium.Web.Proxy.Shared; 7 | 8 | /// 9 | /// Literals shared by Proxy Server 10 | /// 11 | internal class ProxyConstants 12 | { 13 | internal static readonly char DotSplit = '.'; 14 | 15 | internal static readonly string NewLine = "\r\n"; 16 | internal static readonly byte[] NewLineBytes = { (byte)'\r', (byte)'\n' }; 17 | 18 | internal static readonly HashSet ProxySupportedCompressions = 19 | new(StringComparer.OrdinalIgnoreCase) 20 | { 21 | KnownHeaders.ContentEncodingGzip.String, 22 | KnownHeaders.ContentEncodingDeflate.String, 23 | KnownHeaders.ContentEncodingBrotli.String 24 | }; 25 | 26 | internal static readonly Regex CnRemoverRegex = 27 | new(@"^CN\s*=\s*", RegexOptions.IgnoreCase | RegexOptions.Compiled); 28 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/StrongNameKey.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/titanium-web-proxy/902504a324425e4e49fc5ba604c2b7fa172e68ce/src/Titanium.Web.Proxy/StrongNameKey.snk -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/Titanium.Web.Proxy.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461;net60 5 | Titanium.Web.Proxy 6 | false 7 | True 8 | StrongNameKey.snk 9 | False 10 | True 11 | enable 12 | latest 13 | 3.2.2 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 5.0.0 30 | 31 | 32 | 5.0.0 33 | 34 | 35 | 36 | 37 | 38 | True 39 | True 40 | 41 | 42 | True 43 | True 44 | 45 | 46 | True 47 | True 48 | 49 | 50 | True 51 | True 52 | 53 | 54 | True 55 | True 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/WebSocket/WebSocketFrame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace Titanium.Web.Proxy; 5 | 6 | public class WebSocketFrame 7 | { 8 | public bool IsFinal { get; internal set; } 9 | 10 | public WebsocketOpCode OpCode { get; internal set; } 11 | 12 | public ReadOnlyMemory Data { get; internal set; } 13 | 14 | public string GetText() 15 | { 16 | return GetText(Encoding.UTF8); 17 | } 18 | 19 | public string GetText(Encoding encoding) 20 | { 21 | #if NET6_0_OR_GREATER 22 | return encoding.GetString(Data.Span); 23 | #else 24 | return encoding.GetString(Data.ToArray()); 25 | #endif 26 | } 27 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/WebSocket/WebsocketOpCode.cs: -------------------------------------------------------------------------------- 1 | namespace Titanium.Web.Proxy; 2 | 3 | public enum WebsocketOpCode : byte 4 | { 5 | Continuation, 6 | Text, 7 | Binary, 8 | ConnectionClose = 8, 9 | Ping, 10 | Pong 11 | } -------------------------------------------------------------------------------- /src/Titanium.Web.Proxy/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/Titanium.Web.Proxy.IntegrationTests/Helpers/HttpContinueClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Sockets; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using Titanium.Web.Proxy.Helpers; 6 | using Titanium.Web.Proxy.Http; 7 | 8 | namespace Titanium.Web.Proxy.IntegrationTests.Helpers; 9 | 10 | internal class HttpContinueClient 11 | { 12 | private const int WaitTimeout = 500; 13 | 14 | private static readonly Encoding _msgEncoding = HttpHelper.GetEncodingFromContentType(null); 15 | 16 | public async Task Post(string server, int port, string content) 17 | { 18 | var message = _msgEncoding.GetBytes(content); 19 | var client = new TcpClient(server, port); 20 | client.SendTimeout = client.ReceiveTimeout = 500; 21 | 22 | var request = new Request { Method = "POST", RequestUriString = "/", HttpVersion = new Version(1, 1) }; 23 | request.Headers.AddHeader(KnownHeaders.Host, server); 24 | request.Headers.AddHeader(KnownHeaders.ContentLength, message.Length.ToString()); 25 | request.Headers.AddHeader(KnownHeaders.Expect, KnownHeaders.Expect100Continue); 26 | 27 | var header = _msgEncoding.GetBytes(request.HeaderText); 28 | await client.GetStream().WriteAsync(header, 0, header.Length); 29 | 30 | var buffer = new byte[1024]; 31 | var responseMsg = string.Empty; 32 | Response response; 33 | 34 | while ((response = HttpMessageParsing.ParseResponse(responseMsg)) == null) 35 | { 36 | var readTask = client.GetStream().ReadAsync(buffer, 0, 1024); 37 | if (!readTask.Wait(WaitTimeout)) 38 | { 39 | return null; 40 | } 41 | 42 | responseMsg += _msgEncoding.GetString(buffer, 0, readTask.Result); 43 | } 44 | 45 | if (response.StatusCode == 100) 46 | { 47 | await client.GetStream().WriteAsync(message); 48 | 49 | responseMsg = string.Empty; 50 | 51 | while ((response = HttpMessageParsing.ParseResponse(responseMsg)) == null) 52 | { 53 | var readTask = client.GetStream().ReadAsync(buffer, 0, 1024); 54 | if (!readTask.Wait(WaitTimeout)) 55 | { 56 | return null; 57 | } 58 | 59 | responseMsg += _msgEncoding.GetString(buffer, 0, readTask.Result); 60 | } 61 | 62 | return response; 63 | } 64 | 65 | return response; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/Titanium.Web.Proxy.IntegrationTests/Helpers/HttpContinueServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO.Pipelines; 3 | using System.Net; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Connections; 7 | using Titanium.Web.Proxy.Helpers; 8 | using Titanium.Web.Proxy.Http; 9 | 10 | namespace Titanium.Web.Proxy.IntegrationTests.Helpers; 11 | 12 | internal class HttpContinueServer 13 | { 14 | private static readonly Encoding _msgEncoding = HttpHelper.GetEncodingFromContentType(null); 15 | public HttpStatusCode ExpectationResponse; 16 | public string ResponseBody; 17 | 18 | public async Task HandleRequest(ConnectionContext context) 19 | { 20 | var request = await ReadHeaders(context.Transport.Input); 21 | 22 | if (request.ExpectContinue) 23 | { 24 | var respondContinue = new Response 25 | { 26 | HttpVersion = request.HttpVersion, 27 | StatusCode = (int)ExpectationResponse, 28 | StatusDescription = ExpectationResponse.ToString() 29 | }; 30 | await context.Transport.Output.WriteAsync(_msgEncoding.GetBytes(respondContinue.HeaderText)); 31 | 32 | if (ExpectationResponse != HttpStatusCode.Continue) 33 | { 34 | return; 35 | } 36 | } 37 | 38 | request = await ReadBody(request, context.Transport.Input); 39 | 40 | var responseMsg = _msgEncoding.GetBytes(ResponseBody); 41 | var respondOk = new Response(responseMsg) 42 | { 43 | HttpVersion = new Version(1, 1), 44 | StatusCode = (int)HttpStatusCode.OK, 45 | StatusDescription = HttpStatusCode.OK.ToString() 46 | }; 47 | await context.Transport.Output.WriteAsync(_msgEncoding.GetBytes(respondOk.HeaderText)); 48 | await context.Transport.Output.WriteAsync(responseMsg); 49 | context.Transport.Output.Complete(); 50 | } 51 | 52 | private async Task ReadHeaders(PipeReader input) 53 | { 54 | Request request = null; 55 | try 56 | { 57 | var requestMsg = string.Empty; 58 | while ((request = HttpMessageParsing.ParseRequest(requestMsg, false)) == null) 59 | { 60 | var result = await input.ReadAsync(); 61 | foreach (var seg in result.Buffer) 62 | { 63 | requestMsg += _msgEncoding.GetString(seg.Span); 64 | } 65 | 66 | input.AdvanceTo(result.Buffer.End); 67 | } 68 | } 69 | catch (Exception ex) 70 | { 71 | Console.WriteLine($"{ex.GetType()}: {ex.Message}"); 72 | } 73 | 74 | return request; 75 | } 76 | 77 | private async Task ReadBody(Request request, PipeReader input) 78 | { 79 | var msg = request.HeaderText; 80 | try 81 | { 82 | while ((request = HttpMessageParsing.ParseRequest(msg, true)) == null) 83 | { 84 | var result = await input.ReadAsync(); 85 | foreach (var seg in result.Buffer) 86 | { 87 | msg += _msgEncoding.GetString(seg.Span); 88 | } 89 | 90 | input.AdvanceTo(result.Buffer.End); 91 | } 92 | } 93 | catch (Exception ex) 94 | { 95 | Console.WriteLine($"{ex.GetType()}: {ex.Message}"); 96 | } 97 | 98 | return request; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /tests/Titanium.Web.Proxy.IntegrationTests/Helpers/TestHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | 5 | namespace Titanium.Web.Proxy.IntegrationTests.Helpers; 6 | 7 | public static class TestHelper 8 | { 9 | public static HttpClient GetHttpClient(int localProxyPort, 10 | bool enableBasicProxyAuthorization = false) 11 | { 12 | var proxy = new TestProxy($"http://localhost:{localProxyPort}", enableBasicProxyAuthorization); 13 | 14 | var handler = new HttpClientHandler { Proxy = proxy, UseProxy = true }; 15 | 16 | return new HttpClient(handler); 17 | } 18 | 19 | public static HttpClient GetHttpClient() 20 | { 21 | return new HttpClient(new HttpClientHandler()); 22 | } 23 | 24 | public class TestProxy : IWebProxy 25 | { 26 | public TestProxy(string proxyUri, bool enableAuthorization) 27 | : this(new Uri(proxyUri)) 28 | { 29 | if (enableAuthorization) 30 | { 31 | Credentials = new NetworkCredential("test", "Test56"); 32 | } 33 | } 34 | 35 | private TestProxy(Uri proxyUri) 36 | { 37 | ProxyUri = proxyUri; 38 | } 39 | 40 | public Uri ProxyUri { get; set; } 41 | public ICredentials Credentials { get; set; } 42 | 43 | public Uri GetProxy(Uri destination) 44 | { 45 | return ProxyUri; 46 | } 47 | 48 | public bool IsBypassed(Uri host) 49 | { 50 | return false; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/Titanium.Web.Proxy.IntegrationTests/HttpsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace Titanium.Web.Proxy.IntegrationTests; 9 | 10 | [TestClass] 11 | public class HttpsTests 12 | { 13 | [TestMethod] 14 | public async Task Can_Handle_Https_Request() 15 | { 16 | var testSuite = new TestSuite(); 17 | 18 | var server = testSuite.GetServer(); 19 | server.HandleRequest(context => 20 | { 21 | return context.Response.WriteAsync("I am server. I received your greetings."); 22 | }); 23 | 24 | var proxy = testSuite.GetProxy(); 25 | var client = testSuite.GetClient(proxy); 26 | 27 | var response = await client.PostAsync(new Uri(server.ListeningHttpsUrl), 28 | new StringContent("hello server. I am a client.")); 29 | 30 | Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); 31 | var body = await response.Content.ReadAsStringAsync(); 32 | 33 | Assert.AreEqual("I am server. I received your greetings.", body); 34 | } 35 | 36 | [TestMethod] 37 | public async Task Can_Handle_Https_Fake_Tunnel_Request() 38 | { 39 | var testSuite = new TestSuite(); 40 | 41 | var server = testSuite.GetServer(); 42 | server.HandleRequest(context => 43 | { 44 | return context.Response.WriteAsync("I am server. I received your greetings."); 45 | }); 46 | 47 | var proxy = testSuite.GetProxy(); 48 | proxy.BeforeRequest += async (sender, e) => 49 | { 50 | e.HttpClient.Request.Url = server.ListeningHttpUrl; 51 | await Task.FromResult(0); 52 | }; 53 | 54 | var client = testSuite.GetClient(proxy); 55 | 56 | var response = await client.PostAsync(new Uri($"https://{Guid.NewGuid().ToString()}.com"), 57 | new StringContent("hello server. I am a client.")); 58 | 59 | Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); 60 | var body = await response.Content.ReadAsStringAsync(); 61 | 62 | Assert.AreEqual("I am server. I received your greetings.", body); 63 | } 64 | 65 | [TestMethod] 66 | public async Task Can_Handle_Https_Mutual_Tls_Request() 67 | { 68 | var testSuite = new TestSuite(true); 69 | 70 | var server = testSuite.GetServer(); 71 | server.HandleRequest(context => 72 | { 73 | return context.Response.WriteAsync("I am server. I received your greetings."); 74 | }); 75 | 76 | var proxy = testSuite.GetProxy(); 77 | var clientCert = proxy.CertificateManager.CreateCertificate("client.com", false); 78 | 79 | proxy.ClientCertificateSelectionCallback += async (sender, e) => 80 | { 81 | e.ClientCertificate = clientCert; 82 | await Task.CompletedTask; 83 | }; 84 | 85 | var client = testSuite.GetClient(proxy); 86 | 87 | var response = await client.PostAsync(new Uri(server.ListeningHttpsUrl), 88 | new StringContent("hello server. I am a client.")); 89 | 90 | Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); 91 | var body = await response.Content.ReadAsStringAsync(); 92 | 93 | Assert.AreEqual("I am server. I received your greetings.", body); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /tests/Titanium.Web.Proxy.IntegrationTests/Setup/TestProxyServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using Titanium.Web.Proxy.Models; 4 | using Titanium.Web.Proxy.Network; 5 | 6 | namespace Titanium.Web.Proxy.IntegrationTests.Setup; 7 | 8 | public class TestProxyServer : IDisposable 9 | { 10 | public TestProxyServer(bool isReverseProxy, ProxyServer upStreamProxy = null) 11 | { 12 | ProxyServer = new ProxyServer(); 13 | 14 | var explicitEndPoint = isReverseProxy 15 | ? (ProxyEndPoint)new TransparentProxyEndPoint(IPAddress.Any, 0) 16 | : new ExplicitProxyEndPoint(IPAddress.Any, 0); 17 | 18 | ProxyServer.AddEndPoint(explicitEndPoint); 19 | 20 | if (upStreamProxy != null) 21 | { 22 | ProxyServer.UpStreamHttpProxy = new ExternalProxy("localhost", upStreamProxy.ProxyEndPoints[0].Port); 23 | ProxyServer.UpStreamHttpsProxy = new ExternalProxy("localhost", upStreamProxy.ProxyEndPoints[0].Port); 24 | } 25 | 26 | ProxyServer.Start(); 27 | } 28 | 29 | public ProxyServer ProxyServer { get; } 30 | 31 | public int ListeningPort => ProxyServer.ProxyEndPoints[0].Port; 32 | 33 | public CertificateManager CertificateManager => ProxyServer.CertificateManager; 34 | 35 | public void Dispose() 36 | { 37 | ProxyServer.Stop(); 38 | ProxyServer.Dispose(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/Titanium.Web.Proxy.IntegrationTests/Setup/TestSuite.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using Titanium.Web.Proxy.IntegrationTests.Helpers; 3 | using Titanium.Web.Proxy.IntegrationTests.Setup; 4 | 5 | namespace Titanium.Web.Proxy.IntegrationTests; 6 | 7 | public class TestSuite 8 | { 9 | private readonly TestServer server; 10 | 11 | public TestSuite(bool requireMutualTls = false) 12 | { 13 | var dummyProxy = new ProxyServer(); 14 | var serverCertificate = dummyProxy.CertificateManager.CreateServerCertificate("localhost").Result; 15 | server = new TestServer(serverCertificate, requireMutualTls); 16 | } 17 | 18 | public TestServer GetServer() 19 | { 20 | return server; 21 | } 22 | 23 | public ProxyServer GetProxy(ProxyServer upStreamProxy = null) 24 | { 25 | if (upStreamProxy != null) 26 | { 27 | return new TestProxyServer(false, upStreamProxy).ProxyServer; 28 | } 29 | 30 | return new TestProxyServer(false).ProxyServer; 31 | } 32 | 33 | public ProxyServer GetReverseProxy(ProxyServer upStreamProxy = null) 34 | { 35 | if (upStreamProxy != null) 36 | { 37 | return new TestProxyServer(true, upStreamProxy).ProxyServer; 38 | } 39 | 40 | return new TestProxyServer(true).ProxyServer; 41 | } 42 | 43 | public HttpClient GetClient(ProxyServer proxyServer, bool enableBasicProxyAuthorization = false) 44 | { 45 | return TestHelper.GetHttpClient(proxyServer.ProxyEndPoints[0].Port, enableBasicProxyAuthorization); 46 | } 47 | 48 | public HttpClient GetReverseProxyClient() 49 | { 50 | return TestHelper.GetHttpClient(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Titanium.Web.Proxy.IntegrationTests/StressTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace Titanium.Web.Proxy.IntegrationTests; 9 | 10 | [TestClass] 11 | public class StressTests 12 | { 13 | [TestMethod] 14 | [Timeout(2 * 60 * 1000)] 15 | public async Task Stress_Test_With_One_Server_And_Many_Clients() 16 | { 17 | var testSuite = new TestSuite(); 18 | 19 | var server = testSuite.GetServer(); 20 | server.HandleRequest(context => 21 | { 22 | return context.Response.WriteAsync("I am server. I received your greetings."); 23 | }); 24 | 25 | using var proxy = testSuite.GetProxy(); 26 | 27 | await Task.Delay(1000); 28 | 29 | var tasks = new List(); 30 | 31 | //send 1000 requests to server 32 | for (var j = 0; j < 1000; j++) 33 | { 34 | var task = Task.Run(async () => 35 | { 36 | using var client = testSuite.GetClient(proxy); 37 | 38 | await client.PostAsync(new Uri(server.ListeningHttpsUrl), 39 | new StringContent("hello server. I am a client.")); 40 | }); 41 | 42 | tasks.Add(task); 43 | } 44 | 45 | await Task.WhenAll(tasks); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Titanium.Web.Proxy.IntegrationTests/StrongNameKey.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/titanium-web-proxy/902504a324425e4e49fc5ba604c2b7fa172e68ce/tests/Titanium.Web.Proxy.IntegrationTests/StrongNameKey.snk -------------------------------------------------------------------------------- /tests/Titanium.Web.Proxy.IntegrationTests/Titanium.Web.Proxy.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net60 5 | StrongNameKey.snk 6 | true 7 | 8 | 9 | 10 | 11 | PreserveNewest 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/Titanium.Web.Proxy.IntegrationTests/Titanium.Web.Proxy.IntegrationTests.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.136 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Titanium.Web.Proxy.IntegrationTests", "Titanium.Web.Proxy.IntegrationTests.csproj", "{BE0BC910-468F-4F1A-AC3D-F0EC2C75108E}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Titanium.Web.Proxy", "..\..\src\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj", "{F407A98E-290D-4A29-9451-22A6AE380586}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {BE0BC910-468F-4F1A-AC3D-F0EC2C75108E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {BE0BC910-468F-4F1A-AC3D-F0EC2C75108E}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {BE0BC910-468F-4F1A-AC3D-F0EC2C75108E}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {BE0BC910-468F-4F1A-AC3D-F0EC2C75108E}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {F407A98E-290D-4A29-9451-22A6AE380586}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {F407A98E-290D-4A29-9451-22A6AE380586}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {F407A98E-290D-4A29-9451-22A6AE380586}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {F407A98E-290D-4A29-9451-22A6AE380586}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {B518E259-A504-4396-8138-8BFF322FAC80} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /tests/Titanium.Web.Proxy.UnitTests/CertificateManagerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | using Titanium.Web.Proxy.Network; 8 | 9 | namespace Titanium.Web.Proxy.UnitTests 10 | { 11 | [TestClass] 12 | public class CertificateManagerTests 13 | { 14 | private static readonly string[] hostNames 15 | = { "facebook.com", "youtube.com", "google.com", "bing.com", "yahoo.com" }; 16 | 17 | 18 | [TestMethod] 19 | public async Task Simple_BC_Create_Certificate_Test() 20 | { 21 | var tasks = new List(); 22 | 23 | var mgr = new CertificateManager(null, null, false, false, false, new Lazy(() => e => 24 | { 25 | Debug.WriteLine(e.ToString()); 26 | Debug.WriteLine(e.InnerException?.ToString()); 27 | }).Value) 28 | { 29 | CertificateEngine = CertificateEngine.BouncyCastle 30 | }; 31 | mgr.ClearIdleCertificates(); 32 | for (var i = 0; i < 5; i++) 33 | tasks.AddRange(hostNames.Select(host => Task.Run(() => 34 | { 35 | // get the connection 36 | var certificate = mgr.CreateCertificate(host, false); 37 | Assert.IsNotNull(certificate); 38 | }))); 39 | 40 | await Task.WhenAll(tasks.ToArray()); 41 | 42 | mgr.StopClearIdleCertificates(); 43 | } 44 | 45 | // uncomment this to compare WinCert maker performance with BC (BC takes more time for same test above) 46 | //[TestMethod] 47 | public async Task Simple_Create_Win_Certificate_Test() 48 | { 49 | var tasks = new List(); 50 | 51 | var mgr = new CertificateManager(null, null, false, false, false, new Lazy(() => e => 52 | { 53 | Debug.WriteLine(e.ToString()); 54 | Debug.WriteLine(e.InnerException?.ToString()); 55 | }).Value) 56 | { CertificateEngine = CertificateEngine.DefaultWindows }; 57 | 58 | mgr.CreateRootCertificate(); 59 | mgr.TrustRootCertificate(true); 60 | mgr.ClearIdleCertificates(); 61 | 62 | for (var i = 0; i < 5; i++) 63 | tasks.AddRange(hostNames.Select(host => Task.Run(() => 64 | { 65 | // get the connection 66 | var certificate = mgr.CreateCertificate(host, false); 67 | Assert.IsNotNull(certificate); 68 | }))); 69 | 70 | await Task.WhenAll(tasks.ToArray()); 71 | mgr.RemoveTrustedRootCertificate(true); 72 | mgr.StopClearIdleCertificates(); 73 | } 74 | 75 | [TestMethod] 76 | public async Task Create_Server_Certificate_Test() 77 | { 78 | var tasks = new List(); 79 | 80 | var mgr = new CertificateManager(null, null, false, false, false, new Lazy(() => e => 81 | { 82 | Debug.WriteLine(e.ToString()); 83 | Debug.WriteLine(e.InnerException?.ToString()); 84 | }).Value) 85 | { CertificateEngine = CertificateEngine.BouncyCastleFast }; 86 | 87 | mgr.SaveFakeCertificates = true; 88 | 89 | for (var i = 0; i < 500; i++) 90 | tasks.AddRange(hostNames.Select(host => Task.Run(() => 91 | { 92 | var certificate = mgr.CreateServerCertificate(host); 93 | Assert.IsNotNull(certificate); 94 | }))); 95 | 96 | await Task.WhenAll(tasks.ToArray()); 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /tests/Titanium.Web.Proxy.UnitTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyDescription("")] 8 | [assembly: AssemblyCopyright("Copyright © Titanium 2015-2019")] 9 | [assembly: AssemblyTrademark("")] 10 | [assembly: AssemblyCulture("")] 11 | 12 | // Setting ComVisible to false makes the types in this assembly not visible 13 | // to COM components. If you need to access a type in this assembly from 14 | // COM, set the ComVisible attribute to true on that type. 15 | [assembly: ComVisible(false)] 16 | 17 | // The following GUID is for the ID of the typelib if this project is exposed to COM 18 | [assembly: Guid("b517e3d0-d03b-436f-ab03-34ba0d5321af")] -------------------------------------------------------------------------------- /tests/Titanium.Web.Proxy.UnitTests/ProxyServerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Titanium.Web.Proxy.Models; 5 | 6 | namespace Titanium.Web.Proxy.UnitTests 7 | { 8 | [TestClass] 9 | public class ProxyServerTests 10 | { 11 | [TestMethod] 12 | public void 13 | GivenOneEndpointIsAlreadyAddedToAddress_WhenAddingNewEndpointToExistingAddress_ThenExceptionIsThrown() 14 | { 15 | // Arrange 16 | var proxy = new ProxyServer(); 17 | const int port = 9999; 18 | var firstIpAddress = IPAddress.Parse("127.0.0.1"); 19 | var secondIpAddress = IPAddress.Parse("127.0.0.1"); 20 | proxy.AddEndPoint(new ExplicitProxyEndPoint(firstIpAddress, port, false)); 21 | 22 | // Act 23 | try 24 | { 25 | proxy.AddEndPoint(new ExplicitProxyEndPoint(secondIpAddress, port, false)); 26 | } 27 | catch (Exception exc) 28 | { 29 | // Assert 30 | StringAssert.Contains(exc.Message, "Cannot add another endpoint to same port"); 31 | return; 32 | } 33 | 34 | Assert.Fail("An exception should be thrown by now"); 35 | } 36 | 37 | [TestMethod] 38 | public void 39 | GivenOneEndpointIsAlreadyAddedToAddress_WhenAddingNewEndpointToExistingAddress_ThenTwoEndpointsExists() 40 | { 41 | // Arrange 42 | var proxy = new ProxyServer(); 43 | const int port = 9999; 44 | var firstIpAddress = IPAddress.Parse("127.0.0.1"); 45 | var secondIpAddress = IPAddress.Parse("192.168.1.1"); 46 | proxy.AddEndPoint(new ExplicitProxyEndPoint(firstIpAddress, port, false)); 47 | 48 | // Act 49 | proxy.AddEndPoint(new ExplicitProxyEndPoint(secondIpAddress, port, false)); 50 | 51 | // Assert 52 | Assert.AreEqual(2, proxy.ProxyEndPoints.Count); 53 | } 54 | 55 | [TestMethod] 56 | public void GivenOneEndpointIsAlreadyAddedToPort_WhenAddingNewEndpointToExistingPort_ThenExceptionIsThrown() 57 | { 58 | // Arrange 59 | var proxy = new ProxyServer(); 60 | const int port = 9999; 61 | proxy.AddEndPoint(new ExplicitProxyEndPoint(IPAddress.Loopback, port, false)); 62 | 63 | // Act 64 | try 65 | { 66 | proxy.AddEndPoint(new ExplicitProxyEndPoint(IPAddress.Loopback, port, false)); 67 | } 68 | catch (Exception exc) 69 | { 70 | // Assert 71 | StringAssert.Contains(exc.Message, "Cannot add another endpoint to same port"); 72 | return; 73 | } 74 | 75 | Assert.Fail("An exception should be thrown by now"); 76 | } 77 | 78 | [TestMethod] 79 | public void 80 | GivenOneEndpointIsAlreadyAddedToZeroPort_WhenAddingNewEndpointToExistingPort_ThenTwoEndpointsExists() 81 | { 82 | // Arrange 83 | var proxy = new ProxyServer(); 84 | const int port = 0; 85 | proxy.AddEndPoint(new ExplicitProxyEndPoint(IPAddress.Loopback, port, false)); 86 | 87 | // Act 88 | proxy.AddEndPoint(new ExplicitProxyEndPoint(IPAddress.Loopback, port, false)); 89 | 90 | // Assert 91 | Assert.AreEqual(2, proxy.ProxyEndPoints.Count); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /tests/Titanium.Web.Proxy.UnitTests/StrongNameKey.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justcoding121/titanium-web-proxy/902504a324425e4e49fc5ba604c2b7fa172e68ce/tests/Titanium.Web.Proxy.UnitTests/StrongNameKey.snk -------------------------------------------------------------------------------- /tests/Titanium.Web.Proxy.UnitTests/Titanium.Web.Proxy.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net48 5 | true 6 | StrongNameKey.snk 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/Titanium.Web.Proxy.UnitTests/WinAuthTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Titanium.Web.Proxy.Http; 3 | using Titanium.Web.Proxy.Network.WinAuth; 4 | 5 | namespace Titanium.Web.Proxy.UnitTests 6 | { 7 | [TestClass] 8 | public class WinAuthTests 9 | { 10 | [TestMethod] 11 | public void Test_Acquire_Client_Token() 12 | { 13 | var token = WinAuthHandler.GetInitialAuthToken("mylocalserver.com", "NTLM", new InternalDataStore()); 14 | Assert.IsTrue(token.Length > 1); 15 | } 16 | } 17 | } --------------------------------------------------------------------------------