├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SocksSharp.sln ├── appveyor.yml ├── src └── SocksSharp │ ├── Extensions │ ├── HttpHeadersExtensions.cs │ └── StringExtensions.cs │ ├── Helpers │ ├── ContentHelper.cs │ ├── ExceptionHelper.cs │ ├── HostHelper.cs │ └── ReceiveHelper.cs │ ├── Proxy │ ├── Clients │ │ ├── Http.cs │ │ ├── NoProxy.cs │ │ ├── Socks4.cs │ │ ├── Socks4a.cs │ │ └── Socks5.cs │ ├── IProxy.cs │ ├── IProxyClient.cs │ ├── IProxySettings.cs │ ├── IProxySettingsFluent.cs │ ├── ProxyClient.cs │ ├── ProxyException.cs │ ├── ProxySettings.cs │ ├── Request │ │ └── RequestBuilder.cs │ └── Response │ │ ├── IResponseBuilder.cs │ │ └── ResponseBuilder.cs │ ├── ProxyClientHandler.cs │ └── SocksSharp.Core.csproj └── tests └── SocksSharp.Tests ├── NoProxy.Tests.cs ├── ProxyClientTests.cs ├── SocksSharp.Tests.csproj └── proxysettings.json /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | Feel free to [open an issue](https://github.com/extremecodetv/SocksSharp/issues) or submit a [pull request](https://github.com/extremecodetv/SocksSharp/pulls). To make sure your pull request doesn't go in vain (gets declined), open an issue first discussing it (before actually implementing it) 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Artem Dontsov 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![SocksSharp](http://i.imgur.com/hh1aZVU.png) 2 | 3 | # SocksSharp [![AppVeyor](https://img.shields.io/appveyor/ci/extremecodetv/sockssharp/master.svg?style=flat-square)](https://ci.appveyor.com/project/extremecodetv/sockssharp/) [![NuGet](https://img.shields.io/nuget/v/sockssharp.svg?style=flat-square)](https://www.nuget.org/packages/SocksSharp/) [![Codacy](https://img.shields.io/codacy/grade/4f1155d09b794eee84578bf4b7f30a95.svg?style=flat-square)](https://www.codacy.com/app/extremecodetv/SocksSharp) [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/extremecodetv/SocksSharp/master/LICENSE) 4 | 5 | 6 | SocksSharp provides support for Socks4/4a/5 proxy servers to [HttpClient](https://msdn.microsoft.com/en-us/library/system.net.http.httpclient(v=vs.118).aspx) 7 | 8 | ## Installation 9 | 10 | Install as [NuGet package](https://www.nuget.org/packages/Ruri.SocksSharp/): 11 | 12 | ```powershell 13 | Install-Package Ruri.SocksSharp 14 | ``` 15 | 16 | .NET CLI: 17 | 18 | ```shell 19 | dotnet add package Ruri.SocksSharp 20 | ``` 21 | 22 | #### Note about .NET Core 23 | For .NET Core build-time support, you must use the .NET Core 2 SDK. You can target any supported platform in your library, long as the 2.0+ SDK is used at build-time. 24 | 25 | ## Basic Usage 26 | ```C# 27 | var settings = new ProxySettings() 28 | { 29 | Host = "127.0.0.1", 30 | Port = 1080 31 | }; 32 | 33 | using (var proxyClientHandler = new ProxyClientHandler(settings)) 34 | { 35 | using (var httpClient = new HttpClient(proxyClientHandler)) 36 | { 37 | var response = await httpClient.GetAsync("http://example.com/"); 38 | } 39 | } 40 | ``` 41 | 42 | Interesting? [See more](https://github.com/extremecodetv/SocksSharp/wiki) 43 | 44 | ## Contributing 45 | 46 | Feel free to [open an issue](https://github.com/extremecodetv/SocksSharp/issues) or submit a [pull request](https://github.com/extremecodetv/SocksSharp/pulls). To make sure your pull request doesn't go in vain (gets declined), open an issue first discussing it (before actually implementing it). 47 | -------------------------------------------------------------------------------- /SocksSharp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2027 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SocksSharp.Core", "src\SocksSharp\SocksSharp.Core.csproj", "{6D0D29F1-A3E8-479B-8B7A-1A246691D76F}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SocksSharp.Tests", "tests\SocksSharp.Tests\SocksSharp.Tests.csproj", "{45999494-A226-4958-BB58-3ED4A7B29572}" 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 | {6D0D29F1-A3E8-479B-8B7A-1A246691D76F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {6D0D29F1-A3E8-479B-8B7A-1A246691D76F}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {6D0D29F1-A3E8-479B-8B7A-1A246691D76F}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {6D0D29F1-A3E8-479B-8B7A-1A246691D76F}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {45999494-A226-4958-BB58-3ED4A7B29572}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {45999494-A226-4958-BB58-3ED4A7B29572}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {45999494-A226-4958-BB58-3ED4A7B29572}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {45999494-A226-4958-BB58-3ED4A7B29572}.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 = {E9ABC8DC-8E38-4FDE-A2DC-7F8A7A0EBC82} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.1.0-ci-{build} 2 | pull_requests: 3 | do_not_increment_build_number: true 4 | image: Visual Studio 2017 5 | configuration: Debug 6 | before_build: 7 | - nuget restore SocksSharp.sln 8 | build: 9 | publish_nuget: false 10 | publish_nuget_symbols: false 11 | verbosity: normal 12 | 13 | test: off 14 | -------------------------------------------------------------------------------- /src/SocksSharp/Extensions/HttpHeadersExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http.Headers; 5 | 6 | namespace SocksSharp.Extensions 7 | { 8 | internal static class HttpHeadersExtensions 9 | { 10 | private static readonly string[] commaHeaders = new[] { "Accept", "Accept-Encoding" }; 11 | 12 | public static string GetHeaderString(this HttpHeaders headers, string key) 13 | { 14 | if(headers == null) 15 | { 16 | throw new ArgumentNullException(nameof(headers)); 17 | } 18 | 19 | if (string.IsNullOrEmpty(key)) 20 | { 21 | throw new ArgumentNullException(nameof(key)); 22 | } 23 | 24 | string value = string.Empty; 25 | 26 | headers.TryGetValues(key, out IEnumerable values); 27 | 28 | if(values != null && values.Count() > 1) 29 | { 30 | var separator = commaHeaders.Contains(key) ? ", " : " "; 31 | value = string.Join(separator, values.ToArray()); 32 | } 33 | 34 | return value; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/SocksSharp/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2012-2015 Ruslan Khuduev 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | 26 | 27 | namespace SocksSharp.Extensions 28 | { 29 | internal static class StringExtensions 30 | { 31 | public static string Substring(this string str, string left, 32 | int startIndex, StringComparison comparsion = StringComparison.Ordinal) 33 | { 34 | if (string.IsNullOrEmpty(str)) 35 | { 36 | return string.Empty; 37 | } 38 | 39 | #region Проверка параметров 40 | 41 | if (String.IsNullOrEmpty(left)) 42 | { 43 | throw new ArgumentException("Value cannot be null or empty", nameof(left)); 44 | } 45 | 46 | //if (startIndex < 0) 47 | //{ 48 | // throw new ArgumentException("Value cannot be less than zero", nameof(left)); 49 | //} 50 | 51 | //if (startIndex >= str.Length) 52 | //{ 53 | // throw new ArgumentOutOfRangeException("startIndex", 54 | // Resources.ArgumentOutOfRangeException_StringHelper_MoreLengthString); 55 | //} 56 | 57 | #endregion 58 | 59 | // Ищем начало позиции левой подстроки. 60 | int leftPosBegin = str.IndexOf(left, startIndex, comparsion); 61 | 62 | if (leftPosBegin == -1) 63 | { 64 | return string.Empty; 65 | } 66 | 67 | // Вычисляем конец позиции левой подстроки. 68 | int leftPosEnd = leftPosBegin + left.Length; 69 | 70 | // Вычисляем длину найденной подстроки. 71 | int length = str.Length - leftPosEnd; 72 | 73 | return str.Substring(leftPosEnd, length); 74 | } 75 | 76 | public static string Substring(this string str, 77 | string left, StringComparison comparsion = StringComparison.Ordinal) 78 | { 79 | return Substring(str, left, 0, comparsion); 80 | } 81 | 82 | public static string Substring(this string str, string left, string right, 83 | int startIndex, StringComparison comparsion = StringComparison.Ordinal) 84 | { 85 | if (string.IsNullOrEmpty(str)) 86 | { 87 | return string.Empty; 88 | } 89 | 90 | #region Проверка параметров 91 | 92 | if (left == null) 93 | { 94 | throw new ArgumentNullException("left"); 95 | } 96 | 97 | //if (left.Length == 0) 98 | //{ 99 | // throw ExceptionHelper.EmptyString("left"); 100 | //} 101 | 102 | if (right == null) 103 | { 104 | throw new ArgumentNullException("right"); 105 | } 106 | 107 | //if (right.Length == 0) 108 | //{ 109 | // throw ExceptionHelper.EmptyString("right"); 110 | //} 111 | 112 | //if (startIndex < 0) 113 | //{ 114 | // throw ExceptionHelper.CanNotBeLess("startIndex", 0); 115 | //} 116 | 117 | //if (startIndex >= str.Length) 118 | //{ 119 | // throw new ArgumentOutOfRangeException("startIndex", 120 | // Resources.ArgumentOutOfRangeException_StringHelper_MoreLengthString); 121 | //} 122 | 123 | #endregion 124 | 125 | // Ищем начало позиции левой подстроки. 126 | int leftPosBegin = str.IndexOf(left, startIndex, comparsion); 127 | 128 | if (leftPosBegin == -1) 129 | { 130 | return string.Empty; 131 | } 132 | 133 | // Вычисляем конец позиции левой подстроки. 134 | int leftPosEnd = leftPosBegin + left.Length; 135 | 136 | // Ищем начало позиции правой подстроки. 137 | int rightPos = str.IndexOf(right, leftPosEnd, comparsion); 138 | 139 | if (rightPos == -1) 140 | { 141 | return string.Empty; 142 | } 143 | 144 | // Вычисляем длину найденной подстроки. 145 | int length = rightPos - leftPosEnd; 146 | 147 | return str.Substring(leftPosEnd, length); 148 | } 149 | 150 | public static string Substring(this string str, string left, string right, 151 | StringComparison comparsion = StringComparison.Ordinal) 152 | { 153 | return str.Substring(left, right, 0, comparsion); 154 | } 155 | 156 | public static string LastSubstring(this string str, string left, 157 | int startIndex, StringComparison comparsion = StringComparison.Ordinal) 158 | { 159 | if (string.IsNullOrEmpty(str)) 160 | { 161 | return string.Empty; 162 | } 163 | 164 | #region Проверка параметров 165 | 166 | if (left == null) 167 | { 168 | throw new ArgumentNullException("left"); 169 | } 170 | 171 | //if (left.Length == 0) 172 | //{ 173 | // throw ExceptionHelper.EmptyString("left"); 174 | //} 175 | 176 | //if (startIndex < 0) 177 | //{ 178 | // throw ExceptionHelper.CanNotBeLess("startIndex", 0); 179 | //} 180 | 181 | //if (startIndex >= str.Length) 182 | //{ 183 | // throw new ArgumentOutOfRangeException("startIndex", 184 | // Resources.ArgumentOutOfRangeException_StringHelper_MoreLengthString); 185 | //} 186 | 187 | #endregion 188 | 189 | // Ищем начало позиции левой подстроки. 190 | int leftPosBegin = str.LastIndexOf(left, startIndex, comparsion); 191 | 192 | if (leftPosBegin == -1) 193 | { 194 | return string.Empty; 195 | } 196 | 197 | // Вычисляем конец позиции левой подстроки. 198 | int leftPosEnd = leftPosBegin + left.Length; 199 | 200 | // Вычисляем длину найденной подстроки. 201 | int length = str.Length - leftPosEnd; 202 | 203 | return str.Substring(leftPosEnd, length); 204 | } 205 | 206 | public static string LastSubstring(this string str, 207 | string left, StringComparison comparsion = StringComparison.Ordinal) 208 | { 209 | if (string.IsNullOrEmpty(str)) 210 | { 211 | return string.Empty; 212 | } 213 | 214 | return LastSubstring(str, left, str.Length - 1, comparsion); 215 | } 216 | 217 | public static string LastSubstring(this string str, string left, string right, 218 | int startIndex, StringComparison comparsion = StringComparison.Ordinal) 219 | { 220 | if (string.IsNullOrEmpty(str)) 221 | { 222 | return string.Empty; 223 | } 224 | 225 | #region Проверка параметров 226 | 227 | if (left == null) 228 | { 229 | throw new ArgumentNullException("left"); 230 | } 231 | 232 | if (left.Length == 0) 233 | { 234 | //throw ExceptionHelper.EmptyString("left"); 235 | } 236 | 237 | if (right == null) 238 | { 239 | throw new ArgumentNullException("right"); 240 | } 241 | 242 | //if (right.Length == 0) 243 | //{ 244 | // throw ExceptionHelper.EmptyString("right"); 245 | //} 246 | 247 | //if (startIndex < 0) 248 | //{ 249 | // throw ExceptionHelper.CanNotBeLess("startIndex", 0); 250 | //} 251 | 252 | //if (startIndex >= str.Length) 253 | //{ 254 | // throw new ArgumentOutOfRangeException("startIndex", 255 | // Resources.ArgumentOutOfRangeException_StringHelper_MoreLengthString); 256 | //} 257 | 258 | #endregion 259 | 260 | // Ищем начало позиции левой подстроки. 261 | int leftPosBegin = str.LastIndexOf(left, startIndex, comparsion); 262 | 263 | if (leftPosBegin == -1) 264 | { 265 | return string.Empty; 266 | } 267 | 268 | // Вычисляем конец позиции левой подстроки. 269 | int leftPosEnd = leftPosBegin + left.Length; 270 | 271 | // Ищем начало позиции правой подстроки. 272 | int rightPos = str.IndexOf(right, leftPosEnd, comparsion); 273 | 274 | if (rightPos == -1) 275 | { 276 | if (leftPosBegin == 0) 277 | { 278 | return string.Empty; 279 | } 280 | else 281 | { 282 | return LastSubstring(str, left, right, leftPosBegin - 1, comparsion); 283 | } 284 | } 285 | 286 | // Вычисляем длину найденной подстроки. 287 | int length = rightPos - leftPosEnd; 288 | 289 | return str.Substring(leftPosEnd, length); 290 | } 291 | 292 | public static string LastSubstring(this string str, string left, string right, 293 | StringComparison comparsion = StringComparison.Ordinal) 294 | { 295 | if (string.IsNullOrEmpty(str)) 296 | { 297 | return string.Empty; 298 | } 299 | 300 | return str.LastSubstring(left, right, str.Length - 1, comparsion); 301 | } 302 | 303 | public static string[] Substrings(this string str, string left, string right, 304 | int startIndex, StringComparison comparsion = StringComparison.Ordinal) 305 | { 306 | if (string.IsNullOrEmpty(str)) 307 | { 308 | return new string[0]; 309 | } 310 | 311 | #region Проверка параметров 312 | 313 | if (left == null) 314 | { 315 | throw new ArgumentNullException("left"); 316 | } 317 | 318 | if (left.Length == 0) 319 | { 320 | //throw ExceptionHelper.EmptyString("left"); 321 | } 322 | 323 | if (right == null) 324 | { 325 | throw new ArgumentNullException("right"); 326 | } 327 | 328 | if (right.Length == 0) 329 | { 330 | // throw ExceptionHelper.EmptyString("right"); 331 | } 332 | 333 | if (startIndex < 0) 334 | { 335 | // throw ExceptionHelper.CanNotBeLess("startIndex", 0); 336 | } 337 | 338 | if (startIndex >= str.Length) 339 | { 340 | //throw new ArgumentOutOfRangeException("startIndex", 341 | //Resources.ArgumentOutOfRangeException_StringHelper_MoreLengthString); 342 | } 343 | 344 | #endregion 345 | 346 | int currentStartIndex = startIndex; 347 | List strings = new List(); 348 | 349 | while (true) 350 | { 351 | // Ищем начало позиции левой подстроки. 352 | int leftPosBegin = str.IndexOf(left, currentStartIndex, comparsion); 353 | 354 | if (leftPosBegin == -1) 355 | { 356 | break; 357 | } 358 | 359 | // Вычисляем конец позиции левой подстроки. 360 | int leftPosEnd = leftPosBegin + left.Length; 361 | 362 | // Ищем начало позиции правой строки. 363 | int rightPos = str.IndexOf(right, leftPosEnd, comparsion); 364 | 365 | if (rightPos == -1) 366 | { 367 | break; 368 | } 369 | 370 | // Вычисляем длину найденной подстроки. 371 | int length = rightPos - leftPosEnd; 372 | 373 | strings.Add(str.Substring(leftPosEnd, length)); 374 | 375 | // Вычисляем конец позиции правой подстроки. 376 | currentStartIndex = rightPos + right.Length; 377 | } 378 | 379 | return strings.ToArray(); 380 | } 381 | 382 | public static string[] Substrings(this string str, string left, string right, 383 | StringComparison comparsion = StringComparison.Ordinal) 384 | { 385 | return str.Substrings(left, right, 0, comparsion); 386 | } 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /src/SocksSharp/Helpers/ContentHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SocksSharp.Helpers 4 | { 5 | internal static class ContentHelper 6 | { 7 | public static bool IsContentHeader(string name) 8 | { 9 | //https://github.com/dotnet/corefx/blob/3e72ee5971db5d0bd46606fa672969adde29e307/src/System.Net.Http/src/System/Net/Http/Headers/KnownHeaders.cs 10 | var contentHeaders = new [] 11 | { 12 | "Last-Modified", 13 | "Expires", 14 | "Content-Type", 15 | "Content-Range", 16 | "Content-MD5", 17 | "Content-Location", 18 | "Content-Length", 19 | "Content-Language", 20 | "Content-Encoding", 21 | "Allow" 22 | }; 23 | 24 | bool isContent = false; 25 | foreach(var header in contentHeaders) 26 | { 27 | isContent = isContent || header.Equals(name, StringComparison.OrdinalIgnoreCase); 28 | } 29 | 30 | return isContent; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/SocksSharp/Helpers/ExceptionHelper.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace SocksSharp.Helpers 3 | { 4 | internal static class ExceptionHelper 5 | { 6 | public static bool ValidateTcpPort(int port) 7 | { 8 | if (port < 1 || port > 65535) 9 | { 10 | return false; 11 | } 12 | 13 | return true; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/SocksSharp/Helpers/HostHelper.cs: -------------------------------------------------------------------------------- 1 | using SocksSharp.Proxy; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | 8 | namespace SocksSharp.Core.Helpers 9 | { 10 | internal static class HostHelper 11 | { 12 | public static byte[] GetPortBytes(int port) 13 | { 14 | byte[] array = new byte[2]; 15 | 16 | array[0] = (byte)(port / 256); 17 | array[1] = (byte)(port % 256); 18 | 19 | return array; 20 | } 21 | 22 | public static byte[] GetIPAddressBytes(string destinationHost,bool preferIpv4=true) 23 | { 24 | if (!IPAddress.TryParse(destinationHost, out var ipAddr)) 25 | { 26 | try 27 | { 28 | var ips = Dns.GetHostAddresses(destinationHost); 29 | 30 | if (ips.Length > 0) 31 | { 32 | if (preferIpv4) 33 | { 34 | foreach (var ip in ips) 35 | { 36 | var ipBytes = ip.GetAddressBytes(); 37 | if (ipBytes.Length == 4) 38 | { 39 | return ipBytes; 40 | } 41 | } 42 | } 43 | 44 | ipAddr = ips[0]; 45 | } 46 | } 47 | catch (Exception ex) 48 | { 49 | if (ex is SocketException || ex is ArgumentException) 50 | { 51 | throw new ProxyException("Failed to get host address", ex); 52 | } 53 | 54 | throw; 55 | } 56 | } 57 | 58 | return ipAddr.GetAddressBytes(); 59 | } 60 | 61 | public static byte[] GetHostAddressBytes(byte addressType, string host) 62 | { 63 | switch (addressType) 64 | { 65 | case Socks5Constants.AddressTypeIPV4: 66 | case Socks5Constants.AddressTypeIPV6: 67 | return IPAddress.Parse(host).GetAddressBytes(); 68 | 69 | case Socks5Constants.AddressTypeDomainName: 70 | byte[] bytes = new byte[host.Length + 1]; 71 | 72 | bytes[0] = (byte)host.Length; 73 | Encoding.ASCII.GetBytes(host).CopyTo(bytes, 1); 74 | 75 | return bytes; 76 | 77 | default: 78 | return null; 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/SocksSharp/Helpers/ReceiveHelper.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2012-2015 Ruslan Khuduev 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | using System.IO; 25 | using System.Text; 26 | 27 | namespace SocksSharp.Helpers 28 | { 29 | internal sealed class ReceiveHelper 30 | { 31 | #region Fields 32 | 33 | private const int InitialLineSize = 1000; 34 | 35 | private Stream stream; 36 | 37 | private readonly byte[] buffer; 38 | private readonly int bufferSize; 39 | 40 | private int linePosition; 41 | private byte[] lineBuffer = new byte[InitialLineSize]; 42 | 43 | #endregion 44 | 45 | #region Properties 46 | 47 | public bool HasData 48 | { 49 | get 50 | { 51 | return (Length - Position) != 0; 52 | } 53 | } 54 | 55 | public int Length { get; private set; } 56 | 57 | public int Position { get; private set; } 58 | 59 | #endregion 60 | 61 | public ReceiveHelper( int bufferSize) 62 | { 63 | this.bufferSize = bufferSize; 64 | buffer = new byte[bufferSize]; 65 | } 66 | 67 | #region Methods 68 | 69 | public void Init(Stream stream) 70 | { 71 | this.stream = stream; 72 | linePosition = 0; 73 | 74 | Length = 0; 75 | Position = 0; 76 | } 77 | 78 | public string ReadLine() 79 | { 80 | linePosition = 0; 81 | 82 | while (true) 83 | { 84 | if (Position == Length) 85 | { 86 | Position = 0; 87 | Length = stream.Read(buffer, 0, bufferSize); 88 | 89 | if (Length == 0) 90 | { 91 | break; 92 | } 93 | } 94 | 95 | byte b = buffer[Position++]; 96 | 97 | lineBuffer[linePosition++] = b; 98 | 99 | // Если считан символ '\n'. 100 | if (b == 10) 101 | { 102 | break; 103 | } 104 | 105 | // Если достигнут максимальный предел размера буфера линии. 106 | if (linePosition == lineBuffer.Length) 107 | { 108 | // Увеличиваем размер буфера линии в два раза. 109 | byte[] newLineBuffer = new byte[lineBuffer.Length * 2]; 110 | 111 | lineBuffer.CopyTo(newLineBuffer, 0); 112 | lineBuffer = newLineBuffer; 113 | } 114 | } 115 | 116 | return Encoding.ASCII.GetString(lineBuffer, 0, linePosition); 117 | } 118 | 119 | public int Read(byte[] buffer, int index, int length) 120 | { 121 | int curLength = Length - Position; 122 | 123 | if (curLength > length) 124 | { 125 | curLength = length; 126 | } 127 | 128 | Array.Copy(this.buffer, Position, buffer, index, curLength); 129 | 130 | Position += curLength; 131 | 132 | return curLength; 133 | } 134 | 135 | #endregion 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/SocksSharp/Proxy/Clients/Http.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Text; 5 | using System.Net.Sockets; 6 | using SocksSharp.Helpers; 7 | using System.Threading; 8 | using System.Text.RegularExpressions; 9 | 10 | namespace SocksSharp.Proxy 11 | { 12 | public class Http : IProxy 13 | { 14 | public IProxySettings Settings { get; set; } 15 | public string ProtocolVersion { get; set; } = "1.1"; 16 | 17 | /// 18 | /// Create connection to destination host via proxy server. 19 | /// 20 | /// Host 21 | /// Port 22 | /// Connection with proxy server. 23 | /// Connection to destination host 24 | /// Value of is or empty. 25 | /// Value of less than 1 or greater than 65535. 26 | /// Error while working with proxy. 27 | public TcpClient CreateConnection(string destinationHost, int destinationPort, TcpClient client) 28 | { 29 | if (String.IsNullOrEmpty(destinationHost)) 30 | { 31 | throw new ArgumentException(nameof(destinationHost)); 32 | } 33 | 34 | if (!ExceptionHelper.ValidateTcpPort(destinationPort)) 35 | { 36 | throw new ArgumentOutOfRangeException(nameof(destinationPort)); 37 | } 38 | 39 | if (client == null || !client.Connected) 40 | { 41 | throw new SocketException(); 42 | } 43 | 44 | HttpStatusCode statusCode; 45 | 46 | try 47 | { 48 | NetworkStream nStream = client.GetStream(); 49 | 50 | SendConnectionCommand(nStream, destinationHost, destinationPort); 51 | statusCode = ReceiveResponse(nStream); 52 | } 53 | catch (Exception ex) 54 | { 55 | client.Close(); 56 | 57 | if (ex is IOException || ex is SocketException) 58 | { 59 | throw new ProxyException("Error while working with proxy", ex); 60 | } 61 | 62 | throw; 63 | } 64 | 65 | if (statusCode == HttpStatusCode.OK) 66 | return client; 67 | 68 | client.Close(); 69 | throw new ProxyException("The proxy didn't reply with 200 OK"); 70 | } 71 | 72 | #region Methods (private) 73 | 74 | private void SendConnectionCommand(Stream nStream, string destinationHost, int destinationPort) 75 | { 76 | var commandBuilder = new StringBuilder(); 77 | 78 | commandBuilder.AppendFormat("CONNECT {0}:{1} HTTP/{2}\r\n", destinationHost, destinationPort, ProtocolVersion); 79 | commandBuilder.AppendFormat(GenerateAuthorizationHeader()); 80 | commandBuilder.AppendLine(); 81 | 82 | var buffer = Encoding.ASCII.GetBytes(commandBuilder.ToString()); 83 | 84 | nStream.Write(buffer, 0, buffer.Length); 85 | } 86 | 87 | private string GenerateAuthorizationHeader() 88 | { 89 | if (Settings.Credentials == null || 90 | (string.IsNullOrEmpty(Settings.Credentials.UserName) && string.IsNullOrEmpty(Settings.Credentials.Password))) 91 | return string.Empty; 92 | 93 | string data = Convert.ToBase64String(Encoding.UTF8.GetBytes( 94 | $"{Settings.Credentials.UserName}:{Settings.Credentials.Password}")); 95 | 96 | return $"Proxy-Authorization: Basic {data}\r\n"; 97 | } 98 | 99 | private HttpStatusCode ReceiveResponse(NetworkStream nStream) 100 | { 101 | var buffer = new byte[50]; 102 | var responseBuilder = new StringBuilder(); 103 | 104 | WaitData(nStream); 105 | 106 | do 107 | { 108 | int bytesRead = nStream.Read(buffer, 0, buffer.Length); 109 | responseBuilder.Append(Encoding.ASCII.GetString(buffer, 0, bytesRead)); 110 | } while (nStream.DataAvailable); 111 | 112 | string response = responseBuilder.ToString(); 113 | 114 | if (response.Length == 0) 115 | throw new ProxyException("Received empty response"); 116 | 117 | // Выделяем строку статуса. Пример: HTTP/1.1 200 OK\r\n 118 | var match = Regex.Match(response, "HTTP/[0-9\\.]* ([0-9]{3})"); 119 | 120 | if (!match.Success) 121 | throw new ProxyException("Received wrong response from proxy"); 122 | 123 | if (!Enum.TryParse(match.Groups[1].Value, out HttpStatusCode statusCode)) 124 | throw new ProxyException("Invalid status code"); 125 | 126 | return statusCode; 127 | } 128 | 129 | private void WaitData(NetworkStream nStream) 130 | { 131 | int sleepTime = 0; 132 | int delay = nStream.ReadTimeout < 10 ? 133 | 10 : nStream.ReadTimeout; 134 | 135 | while (!nStream.DataAvailable) 136 | { 137 | if (sleepTime >= delay) 138 | throw new ProxyException("Timed out while waiting for data from proxy"); 139 | 140 | sleepTime += 10; 141 | Thread.Sleep(10); 142 | } 143 | } 144 | 145 | #endregion 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/SocksSharp/Proxy/Clients/NoProxy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Sockets; 3 | using SocksSharp.Helpers; 4 | 5 | namespace SocksSharp.Proxy 6 | { 7 | public class NoProxy : IProxy 8 | { 9 | public IProxySettings Settings { get; set; } 10 | public string ProtocolVersion { get; set; } = "1.1"; 11 | 12 | /// 13 | /// Simply returns the existing connection without doing anything. 14 | /// 15 | /// Host 16 | /// Port 17 | /// Connection with proxy server. 18 | /// Connection to destination host 19 | /// Value of is or empty. 20 | /// Value of less than 1 or greater than 65535. 21 | /// Error while working with proxy. 22 | public TcpClient CreateConnection(string destinationHost, int destinationPort, TcpClient client) 23 | { 24 | if (String.IsNullOrEmpty(destinationHost)) 25 | { 26 | throw new ArgumentException(nameof(destinationHost)); 27 | } 28 | 29 | if (!ExceptionHelper.ValidateTcpPort(destinationPort)) 30 | { 31 | throw new ArgumentOutOfRangeException(nameof(destinationPort)); 32 | } 33 | 34 | if (client == null || !client.Connected) 35 | { 36 | throw new SocketException(); 37 | } 38 | 39 | return client; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/SocksSharp/Proxy/Clients/Socks4.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2012-2015 Ruslan Khuduev 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | using System.IO; 25 | using System.Text; 26 | using System.Net.Sockets; 27 | using SocksSharp.Helpers; 28 | using SocksSharp.Core.Helpers; 29 | 30 | using static SocksSharp.Proxy.Socks4Constants; 31 | 32 | namespace SocksSharp.Proxy 33 | { 34 | public static class Socks4Constants 35 | { 36 | public const byte VersionNumber = 4; 37 | public const byte CommandConnect = 0x01; 38 | public const byte CommandBind = 0x02; 39 | public const byte CommandReplyRequestGranted = 0x5a; 40 | public const byte CommandReplyRequestRejectedOrFailed = 0x5b; 41 | public const byte CommandReplyRequestRejectedCannotConnectToIdentd = 0x5c; 42 | public const byte CommandReplyRequestRejectedDifferentIdentd = 0x5d; 43 | } 44 | 45 | public class Socks4 : IProxy 46 | { 47 | internal protected const int DefaultPort = 1080; 48 | 49 | public IProxySettings Settings { get; set; } 50 | 51 | /// 52 | /// Create connection to destination host via proxy server. 53 | /// 54 | /// Host 55 | /// Port 56 | /// Connection with proxy server. 57 | /// Connection to destination host 58 | /// Value of is or empty. 59 | /// Value of less than 1 or greater than 65535. 60 | /// Error while working with proxy. 61 | public TcpClient CreateConnection(string destinationHost, int destinationPort, TcpClient client) 62 | { 63 | if (String.IsNullOrEmpty(destinationHost)) 64 | { 65 | throw new ArgumentException(nameof(destinationHost)); 66 | } 67 | 68 | if (!ExceptionHelper.ValidateTcpPort(destinationPort)) 69 | { 70 | throw new ArgumentOutOfRangeException(nameof(destinationPort)); 71 | } 72 | 73 | if (client == null || !client.Connected) 74 | { 75 | throw new SocketException(); 76 | } 77 | 78 | try 79 | { 80 | SendCommand(client.GetStream(), CommandConnect, destinationHost, destinationPort); 81 | } 82 | catch (Exception ex) 83 | { 84 | client.Close(); 85 | 86 | if (ex is IOException || ex is SocketException) 87 | { 88 | throw new ProxyException("Error while working with proxy", ex); 89 | } 90 | 91 | throw; 92 | } 93 | 94 | return client; 95 | } 96 | 97 | #region Methods (protected) 98 | 99 | internal protected virtual void SendCommand(NetworkStream nStream, byte command, string destinationHost, int destinationPort) 100 | { 101 | var dstIp = HostHelper.GetIPAddressBytes(destinationHost); 102 | var dstPort = HostHelper.GetPortBytes(destinationPort); 103 | 104 | byte[] userId = new byte[0]; 105 | if (Settings.Credentials != null) 106 | { 107 | if (!String.IsNullOrEmpty(Settings.Credentials.UserName)) 108 | { 109 | userId = Encoding.ASCII.GetBytes(Settings.Credentials.UserName); 110 | } 111 | } 112 | 113 | // +----+----+----+----+----+----+----+----+----+----+....+----+ 114 | // | VN | CD | DSTPORT | DSTIP | USERID |NULL| 115 | // +----+----+----+----+----+----+----+----+----+----+....+----+ 116 | // 1 1 2 4 variable 1 117 | byte[] request = new byte[9 + userId.Length]; 118 | 119 | request[0] = VersionNumber; 120 | request[1] = command; 121 | dstPort.CopyTo(request, 2); 122 | dstIp.CopyTo(request, 4); 123 | userId.CopyTo(request, 8); 124 | request[8 + userId.Length] = 0x00; 125 | 126 | nStream.Write(request, 0, request.Length); 127 | 128 | // +----+----+----+----+----+----+----+----+ 129 | // | VN | CD | DSTPORT | DSTIP | 130 | // +----+----+----+----+----+----+----+----+ 131 | // 1 1 2 4 132 | byte[] response = new byte[8]; 133 | 134 | nStream.Read(response, 0, response.Length); 135 | 136 | byte reply = response[1]; 137 | 138 | if (reply != CommandReplyRequestGranted) 139 | { 140 | HandleCommandError(reply); 141 | } 142 | } 143 | 144 | internal protected static void HandleCommandError(byte command) 145 | { 146 | string errorMessage; 147 | 148 | switch (command) 149 | { 150 | case CommandReplyRequestRejectedOrFailed: 151 | errorMessage = "Request rejected or failed"; 152 | break; 153 | 154 | case CommandReplyRequestRejectedCannotConnectToIdentd: 155 | errorMessage = "Request rejected: cannot connect to identd"; 156 | break; 157 | 158 | case CommandReplyRequestRejectedDifferentIdentd: 159 | errorMessage = "Request rejected: different identd"; 160 | break; 161 | 162 | default: 163 | errorMessage = "Unknown socks error"; 164 | break; 165 | } 166 | 167 | throw new ProxyException(errorMessage); 168 | } 169 | 170 | #endregion 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/SocksSharp/Proxy/Clients/Socks4a.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2012-2015 Ruslan Khuduev 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | using System.Text; 25 | using System.Net.Sockets; 26 | 27 | using SocksSharp.Core.Helpers; 28 | using static SocksSharp.Proxy.Socks4Constants; 29 | 30 | namespace SocksSharp.Proxy 31 | { 32 | public class Socks4a : Socks4 33 | { 34 | internal protected override void SendCommand(NetworkStream nStream, byte command, string destinationHost, int destinationPort) 35 | { 36 | byte[] dstPort = HostHelper.GetPortBytes(destinationPort); 37 | byte[] dstIp = { 0, 0, 0, 1 }; 38 | 39 | byte[] userId = new byte[0]; 40 | if (Settings.Credentials != null) 41 | { 42 | if (!String.IsNullOrEmpty(Settings.Credentials.UserName)) 43 | { 44 | userId = Encoding.ASCII.GetBytes(Settings.Credentials.UserName); 45 | } 46 | } 47 | 48 | byte[] dstAddr = Encoding.ASCII.GetBytes(destinationHost); 49 | 50 | // +----+----+----+----+----+----+----+----+----+----+....+----+----+----+....+----+ 51 | // | VN | CD | DSTPORT | DSTIP | USERID |NULL| DSTADDR |NULL| 52 | // +----+----+----+----+----+----+----+----+----+----+....+----+----+----+....+----+ 53 | // 1 1 2 4 variable 1 variable 1 54 | byte[] request = new byte[10 + userId.Length + dstAddr.Length]; 55 | 56 | request[0] = VersionNumber; 57 | request[1] = command; 58 | dstPort.CopyTo(request, 2); 59 | dstIp.CopyTo(request, 4); 60 | userId.CopyTo(request, 8); 61 | request[8 + userId.Length] = 0x00; 62 | dstAddr.CopyTo(request, 9 + userId.Length); 63 | request[9 + userId.Length + dstAddr.Length] = 0x00; 64 | 65 | nStream.Write(request, 0, request.Length); 66 | 67 | // +----+----+----+----+----+----+----+----+ 68 | // | VN | CD | DSTPORT | DSTIP | 69 | // +----+----+----+----+----+----+----+----+ 70 | // 1 1 2 4 71 | byte[] response = new byte[8]; 72 | 73 | nStream.Read(response, 0, 8); 74 | 75 | byte reply = response[1]; 76 | 77 | // Если запрос не выполнен. 78 | if (reply != CommandReplyRequestGranted) 79 | { 80 | HandleCommandError(reply); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/SocksSharp/Proxy/Clients/Socks5.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2012-2015 Ruslan Khuduev 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | using System.IO; 25 | using System.Net; 26 | using System.Text; 27 | using System.Net.Sockets; 28 | 29 | using SocksSharp.Helpers; 30 | using SocksSharp.Core.Helpers; 31 | 32 | using static SocksSharp.Proxy.Socks5Constants; 33 | 34 | namespace SocksSharp.Proxy 35 | { 36 | public static class Socks5Constants 37 | { 38 | public const byte VersionNumber = 5; 39 | public const byte Reserved = 0x00; 40 | public const byte AuthMethodNoAuthenticationRequired = 0x00; 41 | public const byte AuthMethodGssapi = 0x01; 42 | public const byte AuthMethodUsernamePassword = 0x02; 43 | public const byte AuthMethodIanaAssignedRangeBegin = 0x03; 44 | public const byte AuthMethodIanaAssignedRangeEnd = 0x7f; 45 | public const byte AuthMethodReservedRangeBegin = 0x80; 46 | public const byte AuthMethodReservedRangeEnd = 0xfe; 47 | public const byte AuthMethodReplyNoAcceptableMethods = 0xff; 48 | public const byte CommandConnect = 0x01; 49 | public const byte CommandBind = 0x02; 50 | public const byte CommandUdpAssociate = 0x03; 51 | public const byte CommandReplySucceeded = 0x00; 52 | public const byte CommandReplyGeneralSocksServerFailure = 0x01; 53 | public const byte CommandReplyConnectionNotAllowedByRuleset = 0x02; 54 | public const byte CommandReplyNetworkUnreachable = 0x03; 55 | public const byte CommandReplyHostUnreachable = 0x04; 56 | public const byte CommandReplyConnectionRefused = 0x05; 57 | public const byte CommandReplyTTLExpired = 0x06; 58 | public const byte CommandReplyCommandNotSupported = 0x07; 59 | public const byte CommandReplyAddressTypeNotSupported = 0x08; 60 | public const byte AddressTypeIPV4 = 0x01; 61 | public const byte AddressTypeDomainName = 0x03; 62 | public const byte AddressTypeIPV6 = 0x04; 63 | } 64 | 65 | public class Socks5 : IProxy 66 | { 67 | private const int DefaultPort = 1080; 68 | 69 | public IProxySettings Settings { get; set; } 70 | 71 | /// 72 | /// Create connection to destination host via proxy server. 73 | /// 74 | /// Host 75 | /// Port 76 | /// Connection with proxy server. 77 | /// Connection to destination host 78 | /// Value of is or empty. 79 | /// Value of less than 1 or greater than 65535. 80 | /// Error while working with proxy. 81 | public TcpClient CreateConnection(string destinationHost, int destinationPort, TcpClient client) 82 | { 83 | if (String.IsNullOrEmpty(destinationHost)) 84 | { 85 | throw new ArgumentException(nameof(destinationHost)); 86 | } 87 | 88 | if (!ExceptionHelper.ValidateTcpPort(destinationPort)) 89 | { 90 | throw new ArgumentOutOfRangeException(nameof(destinationPort)); 91 | } 92 | 93 | if (client == null || !client.Connected) 94 | { 95 | throw new SocketException(); 96 | } 97 | 98 | try 99 | { 100 | NetworkStream nStream = client.GetStream(); 101 | 102 | InitialNegotiation(nStream); 103 | SendCommand(nStream, CommandConnect, destinationHost, destinationPort); 104 | } 105 | catch (Exception ex) 106 | { 107 | client.Close(); 108 | 109 | if (ex is IOException || ex is SocketException) 110 | { 111 | throw new ProxyException("Error while working with proxy", ex); 112 | } 113 | 114 | throw; 115 | } 116 | 117 | return client; 118 | } 119 | 120 | #region Methods (private) 121 | 122 | private void InitialNegotiation(NetworkStream nStream) 123 | { 124 | byte authMethod; 125 | 126 | if (Settings.Credentials != null) 127 | { 128 | authMethod = AuthMethodUsernamePassword; 129 | } 130 | else 131 | { 132 | authMethod = AuthMethodNoAuthenticationRequired; 133 | } 134 | 135 | // +----+----------+----------+ 136 | // |VER | NMETHODS | METHODS | 137 | // +----+----------+----------+ 138 | // | 1 | 1 | 1 to 255 | 139 | // +----+----------+----------+ 140 | byte[] request = new byte[3]; 141 | 142 | request[0] = VersionNumber; 143 | request[1] = 1; 144 | request[2] = authMethod; 145 | 146 | nStream.Write(request, 0, request.Length); 147 | 148 | // +----+--------+ 149 | // |VER | METHOD | 150 | // +----+--------+ 151 | // | 1 | 1 | 152 | // +----+--------+ 153 | byte[] response = new byte[2]; 154 | 155 | nStream.Read(response, 0, response.Length); 156 | 157 | byte reply = response[1]; 158 | 159 | if (authMethod == AuthMethodUsernamePassword && reply == AuthMethodUsernamePassword) 160 | { 161 | SendUsernameAndPassword(nStream); 162 | } 163 | else if (reply != CommandReplySucceeded) 164 | { 165 | HandleCommandError(reply); 166 | } 167 | } 168 | 169 | private void SendUsernameAndPassword(NetworkStream nStream) 170 | { 171 | byte[] uname = String.IsNullOrEmpty(Settings.Credentials.UserName) 172 | ? new byte[0] 173 | : Encoding.ASCII.GetBytes(Settings.Credentials.UserName); 174 | 175 | byte[] passwd = String.IsNullOrEmpty(Settings.Credentials.Password) 176 | ? new byte[0] 177 | : Encoding.ASCII.GetBytes(Settings.Credentials.Password); 178 | 179 | // +----+------+----------+------+----------+ 180 | // |VER | ULEN | UNAME | PLEN | PASSWD | 181 | // +----+------+----------+------+----------+ 182 | // | 1 | 1 | 1 to 255 | 1 | 1 to 255 | 183 | // +----+------+----------+------+----------+ 184 | byte[] request = new byte[uname.Length + passwd.Length + 3]; 185 | 186 | request[0] = 1; 187 | request[1] = (byte)uname.Length; 188 | uname.CopyTo(request, 2); 189 | request[2 + uname.Length] = (byte)passwd.Length; 190 | passwd.CopyTo(request, 3 + uname.Length); 191 | 192 | nStream.Write(request, 0, request.Length); 193 | 194 | // +----+--------+ 195 | // |VER | STATUS | 196 | // +----+--------+ 197 | // | 1 | 1 | 198 | // +----+--------+ 199 | byte[] response = new byte[2]; 200 | 201 | nStream.Read(response, 0, response.Length); 202 | 203 | byte reply = response[1]; 204 | 205 | if (reply != CommandReplySucceeded) 206 | { 207 | throw new ProxyException("Unable to authenticate proxy-server"); 208 | } 209 | } 210 | 211 | private void SendCommand(NetworkStream nStream, byte command, string destinationHost, int destinationPort) 212 | { 213 | byte aTyp = GetAddressType(destinationHost); 214 | byte[] dstAddr = HostHelper.GetHostAddressBytes(aTyp, destinationHost); 215 | byte[] dstPort = HostHelper.GetPortBytes(destinationPort); 216 | 217 | // +----+-----+-------+------+----------+----------+ 218 | // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 219 | // +----+-----+-------+------+----------+----------+ 220 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 221 | // +----+-----+-------+------+----------+----------+ 222 | byte[] request = new byte[4 + dstAddr.Length + 2]; 223 | 224 | request[0] = VersionNumber; 225 | request[1] = command; 226 | request[2] = Reserved; 227 | request[3] = aTyp; 228 | dstAddr.CopyTo(request, 4); 229 | dstPort.CopyTo(request, 4 + dstAddr.Length); 230 | 231 | nStream.Write(request, 0, request.Length); 232 | 233 | // +----+-----+-------+------+----------+----------+ 234 | // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | 235 | // +----+-----+-------+------+----------+----------+ 236 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 237 | // +----+-----+-------+------+----------+----------+ 238 | byte[] response = new byte[255]; 239 | 240 | nStream.Read(response, 0, response.Length); 241 | 242 | byte reply = response[1]; 243 | if (reply != CommandReplySucceeded) 244 | { 245 | HandleCommandError(reply); 246 | } 247 | } 248 | 249 | private static byte GetAddressType(string host) 250 | { 251 | IPAddress ipAddr = null; 252 | 253 | if (!IPAddress.TryParse(host, out ipAddr)) 254 | { 255 | return AddressTypeDomainName; 256 | } 257 | 258 | switch (ipAddr.AddressFamily) 259 | { 260 | case AddressFamily.InterNetwork: 261 | return AddressTypeIPV4; 262 | 263 | case AddressFamily.InterNetworkV6: 264 | return AddressTypeIPV6; 265 | 266 | default: 267 | return 0; 268 | throw new ProxyException(String.Format("Not supported address type {0}", host)); 269 | } 270 | 271 | } 272 | 273 | private static void HandleCommandError(byte command) 274 | { 275 | string errorMessage; 276 | 277 | switch (command) 278 | { 279 | case AuthMethodReplyNoAcceptableMethods: 280 | errorMessage = "Auth failed: not acceptable method"; 281 | break; 282 | 283 | case CommandReplyGeneralSocksServerFailure: 284 | errorMessage = "General socks server failure"; 285 | break; 286 | 287 | case CommandReplyConnectionNotAllowedByRuleset: 288 | errorMessage = "Connection not allowed by ruleset"; 289 | break; 290 | 291 | case CommandReplyNetworkUnreachable: 292 | errorMessage = "Network unreachable"; 293 | break; 294 | 295 | case CommandReplyHostUnreachable: 296 | errorMessage = "Host unreachable"; 297 | break; 298 | 299 | case CommandReplyConnectionRefused: 300 | errorMessage = "Connection refused"; 301 | break; 302 | 303 | case CommandReplyTTLExpired: 304 | errorMessage = "TTL Expired"; 305 | break; 306 | 307 | case CommandReplyCommandNotSupported: 308 | errorMessage = "Command not supported"; 309 | break; 310 | 311 | case CommandReplyAddressTypeNotSupported: 312 | errorMessage = "Address type not supported"; 313 | break; 314 | 315 | default: 316 | errorMessage = "Unknown socks error"; 317 | break; 318 | } 319 | 320 | throw new ProxyException(errorMessage); 321 | } 322 | 323 | #endregion 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/SocksSharp/Proxy/IProxy.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Sockets; 2 | 3 | namespace SocksSharp.Proxy 4 | { 5 | /// 6 | /// Provides an interface for proxy client 7 | /// 8 | public interface IProxy 9 | { 10 | /// 11 | /// Gets or sets proxy settings 12 | /// 13 | IProxySettings Settings { get; set; } 14 | 15 | /// 16 | /// Create connection to destination host via proxy server. 17 | /// 18 | /// Host 19 | /// Port 20 | /// Connection with proxy server. 21 | /// Connection to destination host 22 | TcpClient CreateConnection(string destinationHost, int destinationPort, TcpClient client); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/SocksSharp/Proxy/IProxyClient.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Sockets; 2 | 3 | namespace SocksSharp.Proxy 4 | { 5 | /// 6 | /// Provides an interface for 7 | /// 8 | /// 9 | public interface IProxyClient where T : IProxy 10 | { 11 | /// 12 | /// Gets or sets proxy settings for client 13 | /// 14 | ProxySettings Settings { get; set; } 15 | 16 | /// 17 | /// Create connection via proxy to destination host 18 | /// 19 | /// Destination 20 | NetworkStream GetDestinationStream(string destinationHost, int destinationPort); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/SocksSharp/Proxy/IProxySettings.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace SocksSharp.Proxy 4 | { 5 | /// 6 | /// Provides an interface for proxy settings 7 | /// 8 | public interface IProxySettings 9 | { 10 | /// 11 | /// Gets or sets the credentials to submit to the proxy server for authentication. 12 | /// 13 | NetworkCredential Credentials { get; set; } 14 | 15 | /// 16 | /// Gets or sets a value of host or IP address for the proxy server 17 | /// 18 | string Host { get; set; } 19 | 20 | /// 21 | /// Gets or sets a value of Port for the proxy server 22 | /// 23 | int Port { get; set; } 24 | 25 | /// 26 | /// Gets or sets the amount of time a 27 | /// will wait to connect to the proxy server 28 | /// 29 | int ConnectTimeout { get; set; } 30 | 31 | /// 32 | /// Gets or sets the amount of time a 33 | /// will wait for read or wait data from the proxy server 34 | /// 35 | int ReadWriteTimeOut { get; set; } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/SocksSharp/Proxy/IProxySettingsFluent.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace SocksSharp.Proxy 4 | { 5 | /// 6 | /// Provides an interface for fluent proxy settings 7 | /// 8 | public interface IProxySettingsFluent 9 | { 10 | /// 11 | /// Sets a value of host or IP address for the proxy server 12 | /// 13 | /// Host 14 | /// 15 | IProxySettingsFluent SetHost(string host); 16 | 17 | /// 18 | /// Sets a value of Port for the proxy server 19 | /// 20 | /// Port 21 | /// 22 | IProxySettingsFluent SetPort(int port); 23 | 24 | /// 25 | /// Gets or sets the amount of time a 26 | /// will wait for read or wait data from the proxy server 27 | /// 28 | /// Connection timeout 29 | /// 30 | IProxySettingsFluent SetConnectionTimeout(int connectionTimeout); 31 | 32 | /// 33 | /// Sets the amount of time a 34 | /// will wait to connect to the proxy server 35 | /// 36 | /// Read/Write timeout 37 | /// 38 | IProxySettingsFluent SetReadWriteTimeout(int readwriteTimeout); 39 | 40 | /// 41 | /// Sets the credentials to submit to the proxy server for authentication 42 | /// 43 | /// Credential 44 | /// 45 | IProxySettingsFluent SetCredential(NetworkCredential credential); 46 | 47 | /// 48 | /// Sets the credentials to submit to the proxy server for authentication 49 | /// 50 | /// Username 51 | /// Password 52 | /// 53 | IProxySettingsFluent SetCredential(string username, string password); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/SocksSharp/Proxy/ProxyClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Security; 4 | using System.Threading; 5 | using System.Net.Sockets; 6 | 7 | namespace SocksSharp.Proxy 8 | { 9 | /// 10 | /// Represents Proxy Client to 11 | /// 12 | /// 13 | public class ProxyClient : IProxyClient where T : IProxy 14 | { 15 | private readonly T client; 16 | 17 | /// 18 | /// Gets or sets proxy settings for client 19 | /// 20 | public ProxySettings Settings { get; set; } 21 | 22 | /// 23 | /// Initialize a new instance of the with proxy handler 24 | /// 25 | public ProxyClient() 26 | { 27 | this.client = (T) Activator.CreateInstance(typeof(T)); 28 | } 29 | 30 | /// 31 | /// Create connection via proxy to destination host 32 | /// 33 | /// Destination 34 | /// 35 | /// Value of equals or empty. 36 | /// -or- 37 | /// Value of less than 1 or greater than 65535. 38 | /// -or- 39 | /// Value of username length greater than 255. 40 | /// -or- 41 | /// Value of password length greater than 255. 42 | /// 43 | public NetworkStream GetDestinationStream(string destinationHost, int destinationPort) 44 | { 45 | TcpClient tcpClient = new TcpClient() 46 | { 47 | ReceiveTimeout = Settings.ReadWriteTimeOut, 48 | SendTimeout = Settings.ReadWriteTimeOut 49 | }; 50 | 51 | client.Settings = Settings; 52 | Exception connectException = null; 53 | var connectDoneEvent = new ManualResetEventSlim(); 54 | 55 | var host = Settings.Host; 56 | var port = Settings.Port; 57 | 58 | // Added by Ruri for the NoProxy case, connect directly to the server without proxy 59 | if (client is NoProxy) 60 | { 61 | host = destinationHost; 62 | port = destinationPort; 63 | } 64 | 65 | #region Create Connection 66 | 67 | try 68 | { 69 | tcpClient.BeginConnect(host, port, new AsyncCallback( 70 | (ar) => 71 | { 72 | if (tcpClient.Client != null) 73 | { 74 | try 75 | { 76 | tcpClient.EndConnect(ar); 77 | } 78 | catch (Exception ex) 79 | { 80 | connectException = ex; 81 | } 82 | 83 | connectDoneEvent.Set(); 84 | } 85 | }), tcpClient 86 | ); 87 | } 88 | catch (Exception ex) 89 | { 90 | tcpClient.Close(); 91 | 92 | if (ex is SocketException || ex is SecurityException) 93 | { 94 | throw new ProxyException($"Failed to connect to {(client is NoProxy ? "server" : "proxy-server")}", ex); 95 | } 96 | 97 | throw; 98 | } 99 | 100 | if (!connectDoneEvent.Wait(Settings.ConnectTimeout)) 101 | { 102 | tcpClient.Close(); 103 | throw new ProxyException($"Failed to connect to {(client is NoProxy ? "server" : "proxy-server")}"); 104 | } 105 | 106 | if (connectException != null) 107 | { 108 | tcpClient.Close(); 109 | 110 | if (connectException is SocketException) 111 | { 112 | throw new ProxyException($"Failed to connect to {(client is NoProxy ? "server" : "proxy-server")}", connectException); 113 | } 114 | else 115 | { 116 | throw connectException; 117 | } 118 | } 119 | 120 | if (!tcpClient.Connected) 121 | { 122 | tcpClient.Close(); 123 | throw new ProxyException($"Failed to connect to {(client is NoProxy ? "server" : "proxy-server")}"); 124 | } 125 | 126 | #endregion 127 | 128 | tcpClient.SendTimeout = Settings.ReadWriteTimeOut; 129 | tcpClient.ReceiveTimeout = Settings.ReadWriteTimeOut; 130 | 131 | var connectedTcpClient = client.CreateConnection( 132 | destinationHost, 133 | destinationPort, 134 | tcpClient); 135 | 136 | return connectedTcpClient.GetStream(); 137 | } 138 | 139 | } 140 | } -------------------------------------------------------------------------------- /src/SocksSharp/Proxy/ProxyException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SocksSharp.Proxy 4 | { 5 | /// 6 | /// Represents errors that occur during proxy execution. 7 | /// 8 | public class ProxyException : Exception 9 | { 10 | /// 11 | /// Initializes a new instance of the with a specified error message 12 | /// and a reference to the inner exception that is the cause of this exception. 13 | /// 14 | /// The error message that explains the reason for the exception. 15 | public ProxyException(string message) 16 | : base(message) { } 17 | 18 | /// 19 | /// Initializes a new instance of the with a specified error message 20 | /// and a reference to the inner exception that is the cause of this exception. 21 | /// 22 | /// The error message that explains the reason for the exception. 23 | /// The exception that is the cause of the current exception, or a reference. 24 | public ProxyException(string message, Exception innerException) 25 | : base(message, innerException) { } 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/SocksSharp/Proxy/ProxySettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace SocksSharp.Proxy 5 | { 6 | /// 7 | /// Represents the settings for 8 | /// 9 | public class ProxySettings : IProxySettings, IProxySettingsFluent 10 | { 11 | /// 12 | /// Gets or sets the credentials to submit to the proxy server for authentication. 13 | /// 14 | public NetworkCredential Credentials { get; set; } 15 | 16 | /// 17 | /// Gets or sets a value of host or IP address for the proxy server 18 | /// 19 | public string Host { get; set; } 20 | 21 | /// 22 | /// Gets or sets a value of Port for the proxy server 23 | /// 24 | public int Port { get; set; } 25 | 26 | /// 27 | /// Gets or sets the amount of time a 28 | /// will wait to connect to the proxy server 29 | /// 30 | public int ConnectTimeout { get; set; } = 5000; 31 | 32 | /// 33 | /// Gets or sets the amount of time a 34 | /// will wait for read or wait data from the proxy server 35 | /// 36 | public int ReadWriteTimeOut { get; set; } = 10000; 37 | 38 | #region Fluent 39 | 40 | /// 41 | /// Sets the credentials to submit to the proxy server for authentication 42 | /// 43 | /// Credential 44 | /// 45 | public IProxySettingsFluent SetCredential(NetworkCredential credential) 46 | { 47 | Credentials = credential; 48 | return this; 49 | } 50 | 51 | /// 52 | /// Sets the credentials to submit to the proxy server for authentication 53 | /// 54 | /// Username 55 | /// Password 56 | /// 57 | public IProxySettingsFluent SetCredential(string username, string password) 58 | { 59 | Credentials = new NetworkCredential(username, password); 60 | return this; 61 | } 62 | 63 | /// 64 | /// Sets a value of host or IP address for the proxy server 65 | /// 66 | /// Host 67 | /// 68 | public IProxySettingsFluent SetHost(string host) 69 | { 70 | Host = host; 71 | return this; 72 | } 73 | 74 | /// 75 | /// Sets a value of Port for the proxy server 76 | /// 77 | /// Port 78 | /// 79 | public IProxySettingsFluent SetPort(int port) 80 | { 81 | Port = port; 82 | return this; 83 | } 84 | 85 | /// 86 | /// Sets the amount of time a 87 | /// will wait to connect to the proxy server 88 | /// 89 | /// Read/Write timeout 90 | /// 91 | public IProxySettingsFluent SetReadWriteTimeout(int readwriteTimeout) 92 | { 93 | ReadWriteTimeOut = readwriteTimeout; 94 | return this; 95 | } 96 | 97 | /// 98 | /// Gets or sets the amount of time a 99 | /// will wait for read or wait data from the proxy server 100 | /// 101 | /// Connection timeout 102 | /// 103 | public IProxySettingsFluent SetConnectionTimeout(int connectionTimeout) 104 | { 105 | ConnectTimeout = connectionTimeout; 106 | return this; 107 | } 108 | 109 | #endregion 110 | 111 | /// 112 | /// Converts the string representation of a . 113 | /// A return value indicates whether the conversion succeeded. 114 | /// 115 | /// A string containing proxy settings 116 | /// When this method returns, 117 | /// contains the instance of the value equivalent of the number contained in proxy, 118 | /// if the conversion succeeded, or if the conversion failed. 119 | /// 120 | /// if s was converted successfully; otherwise, . 121 | /// String must be in one of this format 122 | /// host:port 123 | /// - or - 124 | /// host:port:username 125 | /// - or - 126 | /// host:port:username:password 127 | /// 128 | public static bool TryParse(string proxy, out ProxySettings proxySettings) 129 | { 130 | NetworkCredential credential = null; 131 | 132 | proxySettings = null; 133 | 134 | #region Parse Address 135 | 136 | if (String.IsNullOrEmpty(proxy)) 137 | { 138 | return false; 139 | } 140 | 141 | string[] values = proxy.Split(':'); 142 | 143 | int port = 1080; 144 | string host = values[0]; 145 | 146 | if (values.Length >= 2) 147 | { 148 | if (!int.TryParse(values[1], out port)) 149 | { 150 | return false; 151 | } 152 | } 153 | #endregion 154 | 155 | #region Parse Credential 156 | 157 | string username = String.Empty; 158 | string password = String.Empty; 159 | 160 | if (values.Length >= 3) 161 | { 162 | credential = new NetworkCredential(); 163 | 164 | username = values[2]; 165 | 166 | if (values.Length >= 4) 167 | { 168 | password = values[3]; 169 | } 170 | 171 | if (!String.IsNullOrEmpty(username)) 172 | { 173 | credential.UserName = username; 174 | } 175 | 176 | if (!String.IsNullOrEmpty(password)) 177 | { 178 | credential.Password = password; 179 | } 180 | } 181 | 182 | #endregion 183 | 184 | proxySettings = new ProxySettings(); 185 | proxySettings.Host = host; 186 | proxySettings.Port = port; 187 | proxySettings.Credentials = credential; 188 | 189 | return true; 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/SocksSharp/Proxy/Request/RequestBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Net.Http; 4 | using System.Collections.Generic; 5 | 6 | using SocksSharp.Extensions; 7 | using SocksSharp.Helpers; 8 | using System.Net.Http.Headers; 9 | using System.Net; 10 | using System.Linq; 11 | 12 | namespace SocksSharp.Proxy.Request 13 | { 14 | internal class RequestBuilder 15 | { 16 | private readonly string newLine = "\r\n"; 17 | 18 | private readonly HttpRequestMessage request; 19 | private readonly CookieContainer cookies; 20 | 21 | 22 | public RequestBuilder(HttpRequestMessage request) : this(request, null) { } 23 | 24 | public RequestBuilder(HttpRequestMessage request, CookieContainer cookies) 25 | { 26 | this.request = request; 27 | this.cookies = cookies; 28 | } 29 | 30 | public byte[] BuildStartingLine() 31 | { 32 | var uri = request.RequestUri; 33 | 34 | var startingLine = $"{request.Method.Method} {uri.PathAndQuery} HTTP/{request.Version}" + newLine; 35 | 36 | if (string.IsNullOrEmpty(request.Headers.Host)) 37 | { 38 | startingLine += "Host: " + uri.Host + newLine; 39 | } 40 | 41 | return ToByteArray(startingLine); 42 | } 43 | 44 | public byte[] BuildHeaders(bool hasContent) 45 | { 46 | var headers = GetHeaders(request.Headers); 47 | if (hasContent) 48 | { 49 | var contentHeaders = GetHeaders(request.Content.Headers); 50 | headers = String.Join(newLine, headers, contentHeaders); 51 | } 52 | 53 | return ToByteArray(headers + newLine + newLine); 54 | } 55 | 56 | private string GetHeaders(HttpHeaders headers) 57 | { 58 | var headersList = new List(); 59 | 60 | foreach (var header in headers) 61 | { 62 | string headerKeyAndValue = String.Empty; 63 | string[] values = header.Value as string[]; 64 | 65 | if (values != null && values.Length < 2) 66 | { 67 | if (values.Length > 0 && !String.IsNullOrEmpty(values[0])) 68 | { 69 | headerKeyAndValue = header.Key + ": " + values[0]; 70 | } 71 | } 72 | else 73 | { 74 | string headerValue = headers.GetHeaderString(header.Key); 75 | if (!String.IsNullOrEmpty(headerValue)) 76 | { 77 | headerKeyAndValue = header.Key + ": " + headerValue; 78 | } 79 | } 80 | 81 | if (!String.IsNullOrEmpty(headerKeyAndValue)) 82 | { 83 | headersList.Add(headerKeyAndValue); 84 | } 85 | } 86 | 87 | if (headers is HttpContentHeaders && !headersList.Any(h => h.StartsWith("Content-Length"))) 88 | { 89 | var content = headers as HttpContentHeaders; 90 | if(content.ContentLength.HasValue && content.ContentLength.Value > 0) 91 | { 92 | headersList.Add($"Content-Length: {content.ContentLength}"); 93 | } 94 | } 95 | 96 | if (headers is not HttpContentHeaders && cookies != null) 97 | { 98 | var cookiesCollection = cookies.GetCookies(request.RequestUri); 99 | var rawCookies = "Cookie: "; 100 | 101 | foreach(var cookie in cookiesCollection) 102 | { 103 | rawCookies += cookie.ToString() + "; "; 104 | } 105 | 106 | if(cookiesCollection.Count > 0) 107 | { 108 | headersList.Add(rawCookies); 109 | } 110 | } 111 | 112 | return String.Join("\r\n", headersList.ToArray()); 113 | } 114 | 115 | private byte[] ToByteArray(string data) 116 | { 117 | return Encoding.ASCII.GetBytes(data); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/SocksSharp/Proxy/Response/IResponseBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Net.Http; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace SocksSharp.Proxy.Response 7 | { 8 | public interface IResponseBuilder 9 | { 10 | int ReceiveTimeout { get; set; } 11 | 12 | Task GetResponseAsync(HttpRequestMessage request, Stream stream); 13 | 14 | Task GetResponseAsync(HttpRequestMessage request, Stream stream, CancellationToken cancellationToken); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/SocksSharp/Proxy/Response/ResponseBuilder.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2012-2015 Ruslan Khuduev 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | using System.IO; 25 | using System.Web; 26 | using System.Net; 27 | using System.Text; 28 | using System.Net.Http; 29 | using System.Threading; 30 | using System.Net.Sockets; 31 | using System.IO.Compression; 32 | using System.Threading.Tasks; 33 | using System.Collections.Generic; 34 | 35 | using SocksSharp.Extensions; 36 | using SocksSharp.Helpers; 37 | 38 | namespace SocksSharp.Proxy.Response 39 | { 40 | internal class ResponseBuilder : IResponseBuilder 41 | { 42 | private sealed class BytesWraper 43 | { 44 | public int Length { get; set; } 45 | 46 | public byte[] Value { get; set; } 47 | } 48 | 49 | private sealed class ZipWraperStream : Stream 50 | { 51 | #region Поля (закрытые) 52 | 53 | private readonly Stream _baseStream; 54 | private readonly ReceiveHelper _receiverHelper; 55 | 56 | #endregion 57 | 58 | 59 | #region Свойства (открытые) 60 | 61 | public int BytesRead { get; private set; } 62 | 63 | public int TotalBytesRead { get; set; } 64 | 65 | public int LimitBytesRead { get; set; } 66 | 67 | #region Переопределённые 68 | 69 | public override bool CanRead 70 | { 71 | get 72 | { 73 | return _baseStream.CanRead; 74 | } 75 | } 76 | 77 | public override bool CanSeek 78 | { 79 | get 80 | { 81 | return _baseStream.CanSeek; 82 | } 83 | } 84 | 85 | public override bool CanTimeout 86 | { 87 | get 88 | { 89 | return _baseStream.CanTimeout; 90 | } 91 | } 92 | 93 | public override bool CanWrite 94 | { 95 | get 96 | { 97 | return _baseStream.CanWrite; 98 | } 99 | } 100 | 101 | public override long Length 102 | { 103 | get 104 | { 105 | return _baseStream.Length; 106 | } 107 | } 108 | 109 | public override long Position 110 | { 111 | get 112 | { 113 | return _baseStream.Position; 114 | } 115 | set 116 | { 117 | _baseStream.Position = value; 118 | } 119 | } 120 | 121 | #endregion 122 | 123 | #endregion 124 | 125 | 126 | public ZipWraperStream(Stream baseStream, ReceiveHelper receiverHelper) 127 | { 128 | _baseStream = baseStream; 129 | _receiverHelper = receiverHelper; 130 | } 131 | 132 | 133 | #region Методы (открытые) 134 | 135 | public override void Flush() 136 | { 137 | _baseStream.Flush(); 138 | } 139 | 140 | public override void SetLength(long value) 141 | { 142 | _baseStream.SetLength(value); 143 | } 144 | 145 | public override long Seek(long offset, SeekOrigin origin) 146 | { 147 | return _baseStream.Seek(offset, origin); 148 | } 149 | 150 | public override int Read(byte[] buffer, int offset, int count) 151 | { 152 | // Если установлен лимит на количество считанных байт. 153 | if (LimitBytesRead != 0) 154 | { 155 | int length = LimitBytesRead - TotalBytesRead; 156 | 157 | // Если лимит достигнут. 158 | if (length == 0) 159 | { 160 | return 0; 161 | } 162 | 163 | if (length > buffer.Length) 164 | { 165 | length = buffer.Length; 166 | } 167 | 168 | if (_receiverHelper.HasData) 169 | { 170 | BytesRead = _receiverHelper.Read(buffer, offset, length); 171 | } 172 | else 173 | { 174 | BytesRead = _baseStream.Read(buffer, offset, length); 175 | } 176 | } 177 | else 178 | { 179 | if (_receiverHelper.HasData) 180 | { 181 | BytesRead = _receiverHelper.Read(buffer, offset, count); 182 | } 183 | else 184 | { 185 | BytesRead = _baseStream.Read(buffer, offset, count); 186 | } 187 | } 188 | 189 | TotalBytesRead += BytesRead; 190 | 191 | return BytesRead; 192 | } 193 | 194 | public override void Write(byte[] buffer, int offset, int count) 195 | { 196 | _baseStream.Write(buffer, offset, count); 197 | } 198 | 199 | #endregion 200 | } 201 | 202 | private static readonly byte[] openHtmlSignature = Encoding.ASCII.GetBytes(""); 204 | 205 | private readonly string newLine = "\r\n"; 206 | private readonly int bufferSize; 207 | 208 | private int contentLength; 209 | 210 | private NetworkStream networkStream; 211 | private Stream commonStream; 212 | 213 | private HttpResponseMessage response; 214 | private Dictionary> contentHeaders; 215 | 216 | private readonly CookieContainer cookies; 217 | private readonly Uri uri; 218 | 219 | private readonly ReceiveHelper receiveHelper; 220 | 221 | private CancellationToken cancellationToken; 222 | 223 | public int ReceiveTimeout { get; set; } 224 | 225 | public ResponseBuilder(int bufferSize, CookieContainer cookies = null, Uri uri = null) 226 | { 227 | this.bufferSize = bufferSize; 228 | 229 | this.cookies = cookies; 230 | this.uri = uri; 231 | 232 | contentLength = -1; 233 | receiveHelper = new ReceiveHelper(bufferSize); 234 | } 235 | 236 | public Task GetResponseAsync(HttpRequestMessage request, Stream stream) 237 | { 238 | return GetResponseAsync(request, stream, CancellationToken.None); 239 | } 240 | 241 | public Task GetResponseAsync(HttpRequestMessage request, Stream stream, CancellationToken cancellationToken) 242 | { 243 | this.networkStream = stream as NetworkStream; 244 | this.commonStream = stream; 245 | 246 | this.cancellationToken = cancellationToken; 247 | 248 | receiveHelper.Init(stream); 249 | 250 | response = new HttpResponseMessage(); 251 | contentHeaders = new Dictionary>(); 252 | 253 | response.RequestMessage = request; 254 | 255 | var task = Task.Run(() => 256 | { 257 | ReceiveStartingLine(); 258 | ReceiveHeaders(); 259 | ReceiveContent(); 260 | 261 | return response; 262 | }); 263 | 264 | return task; 265 | } 266 | 267 | private void ReceiveStartingLine() 268 | { 269 | string startingLine; 270 | while (true) 271 | { 272 | startingLine = receiveHelper.ReadLine(); 273 | if (startingLine.Length == 0) 274 | { 275 | //throw exception; 276 | } 277 | else if (startingLine == newLine) 278 | { 279 | continue; 280 | } 281 | else 282 | { 283 | break; 284 | } 285 | 286 | cancellationToken.ThrowIfCancellationRequested(); 287 | } 288 | 289 | string version = startingLine.Substring("HTTP/", " "); 290 | string statusCode = startingLine.Substring(" ", " "); 291 | if (statusCode.Length == 0) 292 | { 293 | // Если сервер не возвращает Reason Phrase 294 | statusCode = startingLine.Substring(" ", newLine); 295 | } 296 | if (version.Length == 0 || statusCode.Length == 0) 297 | { 298 | throw new ProxyException("Received empty response"); 299 | } 300 | 301 | response.Version = Version.Parse(version); 302 | response.StatusCode = (HttpStatusCode)Enum.Parse(typeof(HttpStatusCode), statusCode); 303 | } 304 | 305 | private void ReceiveHeaders() 306 | { 307 | while (true) 308 | { 309 | string header = receiveHelper.ReadLine(); 310 | 311 | if (header == newLine) 312 | { 313 | return; 314 | } 315 | 316 | int separatorPos = header.IndexOf(':'); 317 | if (separatorPos == -1) 318 | { 319 | //string message = string.Format( 320 | //Resources.HttpException_WrongHeader, header, Address.Host); 321 | //throw NewHttpException(message); 322 | } 323 | string headerName = header.Substring(0, separatorPos); 324 | string headerValue = header.Substring(separatorPos + 1).Trim(' ', '\t', '\r', '\n'); 325 | 326 | if (cookies != null && headerName.Equals("Set-Cookie", StringComparison.OrdinalIgnoreCase)) 327 | { 328 | SetCookie(headerValue); 329 | } 330 | else if (ContentHelper.IsContentHeader(headerName)) 331 | { 332 | List values; 333 | if (contentHeaders.TryGetValue(headerName, out values)) 334 | { 335 | values.Add(headerValue); 336 | } 337 | else 338 | { 339 | values = new List(); 340 | values.Add(headerValue); 341 | 342 | contentHeaders.Add(headerName, values); 343 | } 344 | } 345 | else 346 | { 347 | response.Headers.TryAddWithoutValidation(headerName, headerValue); 348 | } 349 | 350 | 351 | cancellationToken.ThrowIfCancellationRequested(); 352 | } 353 | } 354 | 355 | private void ReceiveContent() 356 | { 357 | if (contentHeaders.Count != 0) 358 | { 359 | contentLength = GetContentLength(); 360 | 361 | 362 | var memoryStream = new MemoryStream( 363 | (contentLength == -1) ? 0 : contentLength); 364 | 365 | try 366 | { 367 | IEnumerable source = GetMessageBodySource(); 368 | foreach (var bytes in source) 369 | { 370 | memoryStream.Write(bytes.Value, 0, bytes.Length); 371 | cancellationToken.ThrowIfCancellationRequested(); 372 | } 373 | } 374 | catch (Exception ex) 375 | { 376 | if (ex is IOException || ex is InvalidOperationException) 377 | { 378 | //throw NewHttpException(Resources.HttpException_FailedReceiveMessageBody, ex); 379 | } 380 | 381 | throw; 382 | } 383 | 384 | memoryStream.Seek(0, SeekOrigin.Begin); 385 | response.Content = new StreamContent(memoryStream); 386 | foreach (var pair in contentHeaders) 387 | { 388 | response.Content.Headers.TryAddWithoutValidation(pair.Key, pair.Value); 389 | } 390 | } 391 | } 392 | 393 | private void SetCookie(string value) 394 | { 395 | if (value.Length == 0) 396 | { 397 | return; 398 | } 399 | 400 | // Ищем позицию, где заканчивается куки и начинается описание его параметров. 401 | int endCookiePos = value.IndexOf(';'); 402 | 403 | // Ищем позицию между именем и значением куки. 404 | int separatorPos = value.IndexOf('='); 405 | 406 | if (separatorPos == -1) 407 | { 408 | throw new InvalidDataException("Invalid cookie"); 409 | } 410 | 411 | string cookieValue; 412 | string cookieName = value.Substring(0, separatorPos); 413 | 414 | if (endCookiePos == -1) 415 | { 416 | cookieValue = value.Substring(separatorPos + 1); 417 | } 418 | else 419 | { 420 | cookieValue = value.Substring(separatorPos + 1, 421 | (endCookiePos - separatorPos) - 1); 422 | 423 | #region Get cookie expires time 424 | 425 | int expiresPos = value.IndexOf("expires="); 426 | 427 | if (expiresPos != -1) 428 | { 429 | string expiresStr; 430 | int endExpiresPos = value.IndexOf(';', expiresPos); 431 | 432 | expiresPos += 8; 433 | 434 | if (endExpiresPos == -1) 435 | { 436 | expiresStr = value.Substring(expiresPos); 437 | } 438 | else 439 | { 440 | expiresStr = value.Substring(expiresPos, endExpiresPos - expiresPos); 441 | } 442 | 443 | DateTime expires; 444 | 445 | // Если время куки вышло, то удаляем её. 446 | if (DateTime.TryParse(expiresStr, out expires) && 447 | expires < DateTime.Now) 448 | { 449 | var collection = cookies.GetCookies(uri); 450 | if (collection[cookieName] != null) 451 | collection[cookieName].Expired = true; 452 | } 453 | } 454 | 455 | #endregion 456 | } 457 | 458 | // Если куки нужно удалить. 459 | if (cookieValue.Length == 0 || 460 | cookieValue.Equals("deleted", StringComparison.OrdinalIgnoreCase)) 461 | { 462 | var collection = cookies.GetCookies(uri); 463 | if (collection[cookieName] != null) 464 | collection[cookieName].Expired = true; 465 | } 466 | else 467 | { 468 | cookies.Add(new Cookie(cookieName, cookieValue, "/", uri.Host)); 469 | } 470 | } 471 | 472 | private IEnumerable GetMessageBodySource() 473 | { 474 | if (contentHeaders.ContainsKey("Content-Encoding")) 475 | { 476 | return GetMessageBodySourceZip(); 477 | } 478 | 479 | return GetMessageBodySourceStd(); 480 | } 481 | 482 | // Загрузка сжатых данных. 483 | private IEnumerable GetMessageBodySourceZip() 484 | { 485 | if (response.Headers.Contains("Transfer-Encoding")) 486 | { 487 | return ReceiveMessageBodyChunkedZip(); 488 | } 489 | 490 | if (contentLength != -1) 491 | { 492 | return ReceiveMessageBodyZip(contentLength); 493 | } 494 | 495 | var streamWrapper = new ZipWraperStream(commonStream, receiveHelper); 496 | 497 | return ReceiveMessageBody(GetZipStream(streamWrapper)); 498 | } 499 | 500 | // Загрузка обычных данных. 501 | private IEnumerable GetMessageBodySourceStd() 502 | { 503 | if (response.Headers.Contains("Transfer-Encoding")) 504 | { 505 | return ReceiveMessageBodyChunked(); 506 | } 507 | if (contentLength != -1) 508 | { 509 | return ReceiveMessageBody(contentLength); 510 | } 511 | 512 | return ReceiveMessageBody(commonStream); 513 | } 514 | 515 | private int GetContentLength() 516 | { 517 | List values; 518 | int length; 519 | 520 | if (contentHeaders.TryGetValue("Content-Length", out values)) 521 | { 522 | if (Int32.TryParse(values[0], out length)) 523 | { 524 | return length; 525 | } 526 | } 527 | 528 | return -1; 529 | } 530 | 531 | private string GetContentEncoding() 532 | { 533 | List values; 534 | string encoding = ""; 535 | 536 | if (contentHeaders.TryGetValue("Content-Encoding", out values)) 537 | { 538 | encoding = values[0]; 539 | } 540 | 541 | return encoding; 542 | } 543 | 544 | private void WaitData() 545 | { 546 | int sleepTime = 0; 547 | int delay = (ReceiveTimeout < 10) ? 548 | 10 : ReceiveTimeout; 549 | 550 | var dataAvailable = networkStream?.DataAvailable; 551 | while (dataAvailable != null && !dataAvailable.Value) 552 | { 553 | if (sleepTime >= delay) 554 | { 555 | throw new ProxyException("Wait data timeout"); 556 | } 557 | 558 | sleepTime += 10; 559 | Thread.Sleep(10); 560 | } 561 | } 562 | 563 | #region Receive Content (F*cking trash, but works (not sure (really))) 564 | 565 | // Загрузка тела сообщения неизвестной длины. 566 | private IEnumerable ReceiveMessageBody(Stream stream) 567 | { 568 | var bytesWraper = new BytesWraper(); 569 | byte[] buffer = new byte[this.bufferSize]; 570 | bytesWraper.Value = buffer; 571 | int begBytesRead = 0; 572 | 573 | // Считываем начальные данные из тела сообщения. 574 | if (stream is GZipStream || stream is DeflateStream) 575 | { 576 | begBytesRead = stream.Read(buffer, 0, bufferSize); 577 | } 578 | else 579 | { 580 | if (receiveHelper.HasData) 581 | { 582 | begBytesRead = receiveHelper.Read(buffer, 0, bufferSize); 583 | } 584 | if (begBytesRead < bufferSize) 585 | { 586 | begBytesRead += stream.Read(buffer, begBytesRead, bufferSize - begBytesRead); 587 | } 588 | } 589 | // Возвращаем начальные данные. 590 | bytesWraper.Length = begBytesRead; 591 | yield return bytesWraper; 592 | // Проверяем, есть ли открывающий тег ''. 594 | bool isHtml = FindSignature(buffer, begBytesRead, openHtmlSignature); 595 | if (isHtml) 596 | { 597 | bool found = FindSignature(buffer, begBytesRead, closeHtmlSignature); 598 | // Проверяем, есть ли в начальных данных закрывающий тег. 599 | if (found) 600 | { 601 | yield break; 602 | } 603 | } 604 | while (true) 605 | { 606 | int bytesRead = stream.Read(buffer, 0, bufferSize); 607 | // Если тело сообщения представляет HTML. 608 | if (isHtml) 609 | { 610 | if (bytesRead == 0) 611 | { 612 | WaitData(); 613 | continue; 614 | } 615 | bool found = FindSignature(buffer, bytesRead, closeHtmlSignature); 616 | if (found) 617 | { 618 | bytesWraper.Length = bytesRead; 619 | yield return bytesWraper; 620 | yield break; 621 | } 622 | } 623 | else if (bytesRead == 0) 624 | { 625 | yield break; 626 | } 627 | bytesWraper.Length = bytesRead; 628 | yield return bytesWraper; 629 | } 630 | } 631 | 632 | // Загрузка тела сообщения известной длины. 633 | private IEnumerable ReceiveMessageBody(int contentLength) 634 | { 635 | //Stream stream = _request.ClientStream; 636 | var bytesWraper = new BytesWraper(); 637 | byte[] buffer = new byte[bufferSize]; 638 | bytesWraper.Value = buffer; 639 | 640 | int totalBytesRead = 0; 641 | while (totalBytesRead != contentLength) 642 | { 643 | int bytesRead; 644 | if (receiveHelper.HasData) 645 | { 646 | bytesRead = receiveHelper.Read(buffer, 0, bufferSize); 647 | } 648 | else 649 | { 650 | bytesRead = commonStream.Read(buffer, 0, bufferSize); 651 | } 652 | if (bytesRead == 0) 653 | { 654 | WaitData(); 655 | } 656 | else 657 | { 658 | totalBytesRead += bytesRead; 659 | bytesWraper.Length = bytesRead; 660 | yield return bytesWraper; 661 | } 662 | } 663 | } 664 | 665 | // Загрузка тела сообщения частями. 666 | private IEnumerable ReceiveMessageBodyChunked() 667 | { 668 | //Stream stream = _request.ClientStream; 669 | var bytesWraper = new BytesWraper(); 670 | byte[] buffer = new byte[this.bufferSize]; 671 | bytesWraper.Value = buffer; 672 | while (true) 673 | { 674 | string line = receiveHelper.ReadLine(); 675 | // Если достигнут конец блока. 676 | if (line == newLine) 677 | { 678 | continue; 679 | } 680 | 681 | line = line.Trim(' ', '\r', '\n'); 682 | // Если достигнут конец тела сообщения. 683 | if (line == string.Empty) 684 | { 685 | yield break; 686 | } 687 | 688 | int blockLength; 689 | int totalBytesRead = 0; 690 | #region Задаём длину блока 691 | try 692 | { 693 | blockLength = Convert.ToInt32(line, 16); 694 | } 695 | catch (Exception ex) 696 | { 697 | if (ex is FormatException || ex is OverflowException) 698 | { 699 | //throw NewHttpException(string.Format( 700 | //Resources.HttpException_WrongChunkedBlockLength, line), ex); 701 | } 702 | throw; 703 | } 704 | #endregion 705 | // Если достигнут конец тела сообщения. 706 | if (blockLength == 0) 707 | { 708 | yield break; 709 | } 710 | 711 | while (totalBytesRead != blockLength) 712 | { 713 | int length = blockLength - totalBytesRead; 714 | if (length > bufferSize) 715 | { 716 | length = bufferSize; 717 | } 718 | int bytesRead; 719 | if (receiveHelper.HasData) 720 | { 721 | bytesRead = receiveHelper.Read(buffer, 0, length); 722 | } 723 | else 724 | { 725 | bytesRead = commonStream.Read(buffer, 0, length); 726 | } 727 | if (bytesRead == 0) 728 | { 729 | WaitData(); 730 | } 731 | else 732 | { 733 | totalBytesRead += bytesRead; 734 | bytesWraper.Length = bytesRead; 735 | yield return bytesWraper; 736 | } 737 | } 738 | } 739 | } 740 | 741 | private IEnumerable ReceiveMessageBodyZip(int contentLength) 742 | { 743 | var bytesWraper = new BytesWraper(); 744 | var streamWrapper = new ZipWraperStream(commonStream, receiveHelper); 745 | using (Stream stream = GetZipStream(streamWrapper)) 746 | { 747 | byte[] buffer = new byte[bufferSize]; 748 | bytesWraper.Value = buffer; 749 | 750 | while (true) 751 | { 752 | int bytesRead = stream.Read(buffer, 0, bufferSize); 753 | if (bytesRead == 0) 754 | { 755 | if (streamWrapper.TotalBytesRead == contentLength) 756 | { 757 | yield break; 758 | } 759 | else 760 | { 761 | WaitData(); 762 | continue; 763 | } 764 | } 765 | bytesWraper.Length = bytesRead; 766 | yield return bytesWraper; 767 | } 768 | } 769 | } 770 | 771 | private IEnumerable ReceiveMessageBodyChunkedZip() 772 | { 773 | var bytesWraper = new BytesWraper(); 774 | var streamWrapper = new ZipWraperStream(commonStream, receiveHelper); 775 | 776 | using (Stream stream = GetZipStream(streamWrapper)) 777 | { 778 | byte[] buffer = new byte[bufferSize]; 779 | bytesWraper.Value = buffer; 780 | while (true) 781 | { 782 | string line = receiveHelper.ReadLine(); 783 | // Если достигнут конец блока. 784 | if (line == newLine) 785 | { 786 | continue; 787 | } 788 | 789 | line = line.Trim(' ', '\r', '\n'); 790 | // Если достигнут конец тела сообщения. 791 | if (line == string.Empty) 792 | { 793 | yield break; 794 | } 795 | 796 | int blockLength; 797 | #region Задаём длину блока 798 | try 799 | { 800 | blockLength = Convert.ToInt32(line, 16); 801 | } 802 | catch (Exception ex) 803 | { 804 | if (ex is FormatException || ex is OverflowException) 805 | { 806 | //throw NewHttpException(string.Format( 807 | //Resources.HttpException_WrongChunkedBlockLength, line), ex); 808 | } 809 | throw; 810 | } 811 | #endregion 812 | // Если достигнут конец тела сообщения. 813 | if (blockLength == 0) 814 | { 815 | yield break; 816 | } 817 | 818 | streamWrapper.TotalBytesRead = 0; 819 | streamWrapper.LimitBytesRead = blockLength; 820 | while (true) 821 | { 822 | int bytesRead = stream.Read(buffer, 0, bufferSize); 823 | if (bytesRead == 0) 824 | { 825 | if (streamWrapper.TotalBytesRead == blockLength) 826 | { 827 | break; 828 | } 829 | else 830 | { 831 | WaitData(); 832 | continue; 833 | } 834 | } 835 | bytesWraper.Length = bytesRead; 836 | yield return bytesWraper; 837 | } 838 | } 839 | } 840 | } 841 | 842 | private Stream GetZipStream(Stream stream) 843 | { 844 | string contentEncoding = GetContentEncoding().ToLower(); 845 | 846 | switch (contentEncoding) 847 | { 848 | case "gzip": 849 | return new GZipStream(stream, CompressionMode.Decompress, true); 850 | case "deflate": 851 | return new DeflateStream(stream, CompressionMode.Decompress, true); 852 | default: 853 | throw new InvalidOperationException($"'{contentEncoding}' not supported encoding format"); 854 | } 855 | } 856 | 857 | private bool FindSignature(byte[] source, int sourceLength, byte[] signature) 858 | { 859 | int length = (sourceLength - signature.Length) + 1; 860 | for (int sourceIndex = 0; sourceIndex < length; ++sourceIndex) 861 | { 862 | for (int signatureIndex = 0; signatureIndex < signature.Length; ++signatureIndex) 863 | { 864 | byte sourceByte = source[signatureIndex + sourceIndex]; 865 | char sourceChar = (char)sourceByte; 866 | if (char.IsLetter(sourceChar)) 867 | { 868 | sourceChar = char.ToLower(sourceChar); 869 | } 870 | sourceByte = (byte)sourceChar; 871 | if (sourceByte != signature[signatureIndex]) 872 | { 873 | break; 874 | } 875 | else if (signatureIndex == (signature.Length - 1)) 876 | { 877 | return true; 878 | } 879 | } 880 | } 881 | return false; 882 | } 883 | 884 | #endregion 885 | } 886 | } 887 | -------------------------------------------------------------------------------- /src/SocksSharp/ProxyClientHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Net.Sockets; 6 | using System.Net.Security; 7 | using System.Security.Authentication; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using SocksSharp.Proxy; 11 | using SocksSharp.Proxy.Request; 12 | using SocksSharp.Proxy.Response; 13 | using System.Security.Cryptography.X509Certificates; 14 | using System.Collections.Generic; 15 | 16 | namespace SocksSharp 17 | { 18 | /// 19 | /// Represents with 20 | /// to provide the support for proxy type 21 | /// 22 | /// 23 | public class ProxyClientHandler : HttpMessageHandler, IDisposable where T : IProxy 24 | { 25 | private readonly IProxyClient proxyClient; 26 | 27 | private Stream connectionCommonStream; 28 | private NetworkStream connectionNetworkStream; 29 | 30 | #region Properties 31 | 32 | /// 33 | /// Gets a current ProxyClient 34 | /// 35 | public IProxyClient Proxy => proxyClient; 36 | 37 | /// 38 | /// Gets a value that indicates whether the handler uses a proxy for requests. 39 | /// 40 | public bool UseProxy => true; 41 | 42 | /// 43 | /// Gets a value that indicates whether the handler supports proxy settings. 44 | /// 45 | public bool SupportsProxy => true; 46 | 47 | /// 48 | /// Gets a value that indicates whether the handler should follow redirection responses. 49 | /// 50 | public bool AllowAutoRedirect { get; set; } 51 | 52 | /// 53 | /// Gets a value that indicates whether the handler supports 54 | /// configuration settings for the 55 | /// 56 | public bool SupportsRedirectConfiguration => true; 57 | 58 | /// 59 | /// The allowed SSL or TLS protocols. 60 | /// 61 | public SslProtocols SslProtocols { get; set; } = SslProtocols.None; 62 | 63 | /// 64 | /// If true, will be used instead of the default ones. 65 | /// 66 | public bool UseCustomCipherSuites { get; set; } = false; 67 | 68 | /// 69 | /// The cipher suites to send to the server during the TLS handshake, in order. 70 | /// The default value of this property contains the cipher suites sent by Firefox as of 21 Dec 2020. 71 | /// 72 | public TlsCipherSuite[] AllowedCipherSuites { get; set; } = new TlsCipherSuite[] 73 | { 74 | TlsCipherSuite.TLS_AES_128_GCM_SHA256, 75 | TlsCipherSuite.TLS_CHACHA20_POLY1305_SHA256, 76 | TlsCipherSuite.TLS_AES_256_GCM_SHA384, 77 | TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 78 | TlsCipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 79 | TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 80 | TlsCipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 81 | TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 82 | TlsCipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 83 | TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, 84 | TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, 85 | TlsCipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 86 | TlsCipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 87 | TlsCipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256, 88 | TlsCipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384, 89 | TlsCipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, 90 | TlsCipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA, 91 | TlsCipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA 92 | }; 93 | 94 | /// 95 | /// Gets the type of decompression method used by the handler for automatic 96 | /// decompression of the HTTP content response. 97 | /// 98 | /// 99 | /// Support GZip and Deflate encoding automatically 100 | /// 101 | public DecompressionMethods AutomaticDecompression 102 | { 103 | get => DecompressionMethods.GZip | DecompressionMethods.Deflate; 104 | } 105 | 106 | /// 107 | /// Gets or sets a value that indicates whether the handler uses the CookieContainer 108 | /// property to store server cookies and uses these cookies when sending requests. 109 | /// 110 | public bool UseCookies { get; set; } 111 | 112 | /// 113 | /// Gets or sets the cookie container used to store server cookies by the handler. 114 | /// 115 | public CookieContainer CookieContainer { get; set; } 116 | 117 | /// 118 | /// Gets or sets delegate to verifies the remote Secure Sockets Layer (SSL) 119 | /// certificate used for authentication. 120 | /// 121 | public RemoteCertificateValidationCallback ServerCertificateCustomValidationCallback { get; set; } 122 | 123 | /// 124 | /// Gets or sets the X509 certificate revocation mode. 125 | /// 126 | public X509RevocationMode CertRevocationMode { get; set; } 127 | 128 | #endregion 129 | 130 | /// 131 | /// Initializes a new instance of the with settings 132 | /// 133 | /// Proxy settings 134 | /// 135 | /// Value of parameter is 136 | /// 137 | public ProxyClientHandler(ProxySettings proxySettings) 138 | { 139 | if (proxySettings == null) 140 | { 141 | throw new ArgumentNullException(nameof(proxySettings)); 142 | } 143 | 144 | this.proxyClient = (IProxyClient) Activator.CreateInstance(typeof(ProxyClient)); 145 | 146 | this.proxyClient.Settings = proxySettings; 147 | } 148 | 149 | /// 150 | /// Creates an instance of HttpResponseMessage based on the information provided in the HttpRequestMessage as an operation that will not block. 151 | /// 152 | /// The HTTP request message. 153 | /// A cancellation token to cancel the operation. 154 | /// Instance of containing http response 155 | protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 156 | { 157 | if (request == null) 158 | { 159 | throw new ArgumentNullException(nameof(request)); 160 | } 161 | 162 | return await Task.Run(async () => 163 | { 164 | if (UseCookies && CookieContainer == null) 165 | { 166 | throw new ArgumentNullException(nameof(CookieContainer)); 167 | } 168 | 169 | await CreateConnection(request, cancellationToken); 170 | await SendDataAsync(request, cancellationToken).ConfigureAwait(false); 171 | var responseMessage = await ReceiveDataAsync(request, cancellationToken).ConfigureAwait(false); 172 | 173 | if ((responseMessage.StatusCode == HttpStatusCode.Moved || 174 | responseMessage.StatusCode == HttpStatusCode.MovedPermanently || 175 | responseMessage.StatusCode == HttpStatusCode.Redirect) && AllowAutoRedirect) 176 | { 177 | request.RequestUri = responseMessage.Headers.Location; 178 | 179 | return await SendAsync(request, cancellationToken).ConfigureAwait(false); 180 | } 181 | 182 | return responseMessage; 183 | }).ConfigureAwait(false); 184 | } 185 | 186 | #region Methods (private) 187 | 188 | private async Task SendDataAsync(HttpRequestMessage request, CancellationToken ct) 189 | { 190 | byte[] buffer; 191 | var hasContent = request.Content != null; 192 | var requestBuilder = UseCookies 193 | ? new RequestBuilder(request, CookieContainer) 194 | : new RequestBuilder(request); 195 | 196 | //Send starting line 197 | buffer = requestBuilder.BuildStartingLine(); 198 | await connectionCommonStream.WriteAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false); 199 | 200 | //Send headers 201 | buffer = requestBuilder.BuildHeaders(hasContent); 202 | await connectionCommonStream.WriteAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false); 203 | if (hasContent) 204 | { 205 | await SendContentAsync(request, ct).ConfigureAwait(false); 206 | } 207 | } 208 | 209 | private async Task ReceiveDataAsync(HttpRequestMessage request, 210 | CancellationToken cancellationToken) 211 | { 212 | var responseBuilder = UseCookies 213 | ? new ResponseBuilder(1024, CookieContainer, request.RequestUri) 214 | : new ResponseBuilder(1024); 215 | return await responseBuilder.GetResponseAsync(request, connectionCommonStream, cancellationToken); 216 | } 217 | 218 | private async Task CreateConnection(HttpRequestMessage request, CancellationToken cancellationToken) 219 | { 220 | Uri uri = request.RequestUri; 221 | connectionNetworkStream = proxyClient.GetDestinationStream(uri.Host, uri.Port); 222 | if (uri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) 223 | { 224 | try 225 | { 226 | SslStream sslStream; 227 | sslStream = new SslStream(connectionNetworkStream, false, ServerCertificateCustomValidationCallback); 228 | 229 | var sslOptions = new SslClientAuthenticationOptions 230 | { 231 | TargetHost = uri.Host, 232 | EnabledSslProtocols = SslProtocols, 233 | CertificateRevocationCheckMode = CertRevocationMode 234 | }; 235 | 236 | if (UseCustomCipherSuites) 237 | sslOptions.CipherSuitesPolicy = new CipherSuitesPolicy(AllowedCipherSuites); 238 | 239 | await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken); 240 | connectionCommonStream = sslStream; 241 | } 242 | catch (Exception ex) 243 | { 244 | if (ex is IOException || ex is AuthenticationException) 245 | { 246 | throw new ProxyException("Failed SSL connect"); 247 | } 248 | 249 | throw; 250 | } 251 | } 252 | else 253 | { 254 | connectionCommonStream = connectionNetworkStream; 255 | } 256 | } 257 | 258 | private async Task SendContentAsync(HttpRequestMessage request, CancellationToken ct) 259 | { 260 | var buffer = await request.Content.ReadAsByteArrayAsync(); 261 | await connectionCommonStream.WriteAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false); 262 | } 263 | 264 | protected override void Dispose(bool disposing) 265 | { 266 | if (disposing) 267 | { 268 | connectionCommonStream?.Dispose(); 269 | connectionNetworkStream?.Dispose(); 270 | } 271 | 272 | base.Dispose(disposing); 273 | } 274 | 275 | #endregion 276 | } 277 | } -------------------------------------------------------------------------------- /src/SocksSharp/SocksSharp.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SocksSharp provides support for Socks4/4a/5 and HTTP proxy servers to HttpClient 5 | Copyright © Artem Dontsov, ExtremeCode, 2017 6 | SocksSharp 7 | 1.1.0 8 | Artem Dontsov, Ruri 9 | net5.0 10 | $(NoWarn);1591 11 | SocksSharp 12 | Ruri.SocksSharp 13 | socks5;socks4;socks4a;http;proxy;httpclient; 14 | https://camo.githubusercontent.com/ad1cacaaf72d2b7b85b7b35de25f5b30941b1a70/687474703a2f2f692e696d6775722e636f6d2f686831615a56552e706e67 15 | https://github.com/extremecodetv/SocksSharp 16 | 2.0.0 17 | false 18 | false 19 | false 20 | 1.6.0 21 | 22 | 23 | 24 | bin\Debug\documentation.xml 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/SocksSharp.Tests/NoProxy.Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Net.Http; 4 | using Xunit; 5 | using SocksSharp.Proxy; 6 | using System.Threading.Tasks; 7 | using Newtonsoft.Json.Linq; 8 | using System.Collections.Generic; 9 | using System.Net; 10 | using System.Net.Security; 11 | using System.Runtime.InteropServices; 12 | using System.Net.Http.Headers; 13 | 14 | namespace SocksSharp.Tests 15 | { 16 | public class NoProxyTests 17 | { 18 | #region Tests 19 | 20 | [Fact] 21 | public async Task RequestHeadersTest() 22 | { 23 | var userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36"; 24 | 25 | var message = new HttpRequestMessage(); 26 | message.Method = HttpMethod.Get; 27 | message.RequestUri = new Uri("http://httpbin.org/user-agent"); 28 | message.Headers.Add("User-Agent", userAgent); 29 | 30 | var response = await GetResponseMessageAsync(message); 31 | 32 | Assert.NotNull(response); 33 | 34 | var userAgentActual = await GetJsonStringValue(response, "user-agent"); 35 | 36 | Assert.NotEmpty(userAgentActual); 37 | Assert.Equal(userAgent, userAgentActual); 38 | } 39 | 40 | [Fact] 41 | public async Task GetRequestTest() 42 | { 43 | var key = "key"; 44 | var value = "value"; 45 | 46 | var message = new HttpRequestMessage(); 47 | message.Method = HttpMethod.Get; 48 | message.RequestUri = new Uri($"http://httpbin.org/get?{key}={value}"); 49 | 50 | var response = await GetResponseMessageAsync(message); 51 | 52 | var actual = await GetJsonDictionaryValue(response, "args"); 53 | 54 | Assert.True(actual.ContainsKey(key)); 55 | Assert.True(actual.ContainsValue(value)); 56 | } 57 | 58 | [Fact] 59 | public async Task GetUtf8Test() 60 | { 61 | var excepted = "∮"; 62 | 63 | var message = new HttpRequestMessage(); 64 | message.Method = HttpMethod.Get; 65 | message.RequestUri = new Uri("http://httpbin.org/encoding/utf8"); 66 | 67 | 68 | var response = await GetResponseMessageAsync(message); 69 | var actual = await response.Content.ReadAsStringAsync(); 70 | 71 | Assert.Contains(excepted, actual); 72 | } 73 | 74 | [Fact] 75 | public async Task GetHtmlPageTest() 76 | { 77 | long exceptedLength = 3741; 78 | var contentType = "text/html"; 79 | var charSet = "utf-8"; 80 | 81 | var message = new HttpRequestMessage(); 82 | message.Method = HttpMethod.Get; 83 | message.RequestUri = new Uri("http://httpbin.org/html"); 84 | 85 | var response = await GetResponseMessageAsync(message); 86 | 87 | var content = response.Content; 88 | Assert.NotNull(content); 89 | 90 | var headers = response.Content.Headers; 91 | Assert.NotNull(headers); 92 | 93 | Assert.NotNull(headers.ContentLength); 94 | Assert.Equal(exceptedLength, headers.ContentLength.Value); 95 | Assert.NotNull(headers.ContentType); 96 | Assert.Equal(contentType, headers.ContentType.MediaType); 97 | Assert.Equal(charSet, headers.ContentType.CharSet); 98 | } 99 | 100 | [Fact] 101 | public async Task DelayTest() 102 | { 103 | var message = new HttpRequestMessage(); 104 | message.Method = HttpMethod.Get; 105 | message.RequestUri = new Uri("http://httpbin.org/delay/4"); 106 | 107 | var response = await GetResponseMessageAsync(message); 108 | var source = response.Content.ReadAsStringAsync(); 109 | 110 | Assert.NotNull(response); 111 | Assert.NotNull(source); 112 | } 113 | 114 | [Fact] 115 | public async Task StreamTest() 116 | { 117 | var message = new HttpRequestMessage(); 118 | message.Method = HttpMethod.Get; 119 | message.RequestUri = new Uri("http://httpbin.org/stream/20"); 120 | 121 | var response = await GetResponseMessageAsync(message); 122 | var source = response.Content.ReadAsStringAsync(); 123 | 124 | Assert.NotNull(response); 125 | Assert.NotNull(source); 126 | } 127 | 128 | [Fact] 129 | public async Task GzipTest() 130 | { 131 | var excepted = "gzip, deflate"; 132 | 133 | var message = new HttpRequestMessage(); 134 | message.Method = HttpMethod.Get; 135 | message.RequestUri = new Uri("http://httpbin.org/gzip"); 136 | 137 | var response = await GetResponseMessageAsync(message); 138 | var acutal = await GetJsonStringValue(response, "Accept-Encoding"); 139 | 140 | Assert.NotNull(response); 141 | Assert.NotNull(acutal); 142 | Assert.Equal(excepted, acutal); 143 | } 144 | 145 | [Fact] 146 | public async Task ReceivedCookiesTest() 147 | { 148 | var name = "name"; 149 | var value = "value"; 150 | 151 | var message = new HttpRequestMessage(); 152 | message.Method = HttpMethod.Get; 153 | message.RequestUri = new Uri($"http://httpbin.org/cookies/set?{name}={value}"); 154 | 155 | var handler = CreateNoProxyHandler(); 156 | handler.CookieContainer = new System.Net.CookieContainer(); 157 | handler.UseCookies = true; 158 | var client = new HttpClient(handler); 159 | HttpResponseMessage response = await client.SendAsync(message); 160 | Assert.NotNull(response); 161 | var cookies = handler.CookieContainer.GetCookies(new Uri("http://httpbin.org/")); 162 | 163 | Assert.Equal(1, cookies.Count); 164 | var cookie = cookies[name]; 165 | Assert.Equal(name, cookie.Name); 166 | Assert.Equal(value, cookie.Value); 167 | 168 | handler.Dispose(); 169 | client.Dispose(); 170 | } 171 | 172 | [Fact] 173 | public async Task SentCookies_WithContent_Test() 174 | { 175 | var name = "name"; 176 | var value = "value"; 177 | var uri = new Uri("http://httpbin.org/cookies"); 178 | 179 | var message = new HttpRequestMessage 180 | { 181 | Method = HttpMethod.Get, 182 | RequestUri = uri 183 | }; 184 | 185 | var handler = CreateNoProxyHandler(); 186 | var cookieContainer = new CookieContainer(); 187 | cookieContainer.Add(uri, new Cookie(name, value)); 188 | handler.CookieContainer = cookieContainer; 189 | handler.UseCookies = true; 190 | var client = new HttpClient(handler); 191 | 192 | message.Content = new StringContent(string.Empty); 193 | message.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/x-www-form-urlencoded"); 194 | 195 | HttpResponseMessage response = await client.SendAsync(message); 196 | Assert.NotNull(response); 197 | 198 | var cookies = await GetJsonDictionaryValue(response, "cookies"); 199 | Assert.Equal(1, cookies.Count); 200 | Assert.True(cookies.ContainsKey(name)); 201 | Assert.Equal(value, cookies[name]); 202 | 203 | handler.Dispose(); 204 | client.Dispose(); 205 | } 206 | 207 | [Fact] 208 | public async Task StatusCodeTest() 209 | { 210 | var code = "404"; 211 | var excepted = "NotFound"; 212 | var message = new HttpRequestMessage(); 213 | message.Method = HttpMethod.Get; 214 | message.RequestUri = new Uri($"http://httpbin.org/status/{code}"); 215 | 216 | var response = await GetResponseMessageAsync(message); 217 | 218 | Assert.NotNull(response); 219 | Assert.Equal(excepted, response.StatusCode.ToString()); 220 | } 221 | 222 | [Fact] 223 | public async Task HttpClient_SendAsync_SetExplicitHostHeader_ShouldNotFail() 224 | { 225 | var message = new HttpRequestMessage(HttpMethod.Get, "https://httpbin.org/headers"); 226 | message.Headers.Host = "httpbin.org"; 227 | 228 | var response = await GetResponseMessageAsync(message).ConfigureAwait(false); 229 | 230 | Assert.NotNull(response); 231 | Assert.Equal(HttpStatusCode.OK, response.StatusCode); 232 | } 233 | 234 | [Fact] 235 | public async Task HttpClient_SendAsync_SetCipherSuites_SendsCorrectly() 236 | { 237 | // Custom cipher suites are not currently supported on Windows as it uses 238 | // SecureChannel for the TLS handshake, and ciphers are set by the windows policies. 239 | // The only known workaround is implementing an OpenSSL / BouncyCastle solution or just using WSL or a linux VM. 240 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 241 | return; 242 | 243 | var message = new HttpRequestMessage(HttpMethod.Get, "https://www.howsmyssl.com/"); 244 | message.Headers.Host = "howsmyssl.com"; 245 | 246 | using var handler = new ProxyClientHandler(new ProxySettings()); 247 | handler.UseCustomCipherSuites = true; 248 | handler.AllowedCipherSuites = new TlsCipherSuite[] 249 | { 250 | TlsCipherSuite.TLS_AES_256_GCM_SHA384, 251 | TlsCipherSuite.TLS_CHACHA20_POLY1305_SHA256, 252 | TlsCipherSuite.TLS_AES_128_GCM_SHA256, 253 | TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 254 | }; 255 | 256 | using var client = new HttpClient(handler); 257 | var response = await client.SendAsync(message); 258 | var page = await response.Content.ReadAsStringAsync(); 259 | 260 | Assert.Contains("TLS_AES_256_GCM_SHA384", page); 261 | Assert.Contains("TLS_CHACHA20_POLY1305_SHA256", page); 262 | Assert.Contains("TLS_AES_128_GCM_SHA256", page); 263 | Assert.Contains("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", page); 264 | 265 | // Make sure it does not contain a cipher suite that we didn't send but that is usually sent by System.Net 266 | Assert.DoesNotContain("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", page); 267 | } 268 | 269 | #endregion 270 | 271 | #region Helpers 272 | 273 | private ProxyClientHandler CreateFiddlerHandler() => new(new() { Host = "127.0.0.1", Port = 8888 }); 274 | private ProxyClientHandler CreateNoProxyHandler() => new(new ProxySettings()); 275 | 276 | private async Task GetJsonStringValue(HttpResponseMessage response, string valueName) 277 | { 278 | var source = await response.Content.ReadAsStringAsync(); 279 | var obj = JObject.Parse(source); 280 | 281 | var result = obj.TryGetValue(valueName, out JToken token); 282 | 283 | if (!result) 284 | { 285 | return String.Empty; 286 | } 287 | 288 | return token.Value(); 289 | } 290 | 291 | private async Task> GetJsonDictionaryValue(HttpResponseMessage response, string valueName) 292 | { 293 | var source = await response.Content.ReadAsStringAsync(); 294 | var obj = JObject.Parse(source); 295 | 296 | var result = obj.TryGetValue(valueName, out JToken token); 297 | 298 | if (!result) 299 | { 300 | return null; 301 | } 302 | 303 | return token.ToObject>(); 304 | } 305 | 306 | private async Task GetResponseMessageAsync(HttpRequestMessage requestMessage) 307 | { 308 | using var handler = CreateNoProxyHandler(); 309 | using var client = new HttpClient(handler); 310 | return await client.SendAsync(requestMessage); 311 | } 312 | 313 | #endregion 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /tests/SocksSharp.Tests/ProxyClientTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Net.Http; 4 | using Microsoft.Extensions.Configuration; 5 | using Xunit; 6 | 7 | using SocksSharp; 8 | using SocksSharp.Proxy; 9 | using System.Threading.Tasks; 10 | using System.IO; 11 | using Newtonsoft.Json; 12 | using Newtonsoft.Json.Linq; 13 | using System.Collections.Generic; 14 | using System.Net; 15 | using System.Net.Security; 16 | using System.Runtime.InteropServices; 17 | 18 | namespace SocksSharp.Tests 19 | { 20 | public class ProxyClientTests 21 | { 22 | private ProxySettings proxySettings; 23 | 24 | public ProxyClientTests() 25 | { 26 | GatherTestConfiguration(); 27 | } 28 | 29 | #region Tests 30 | 31 | [Fact] 32 | public async Task RequestHeadersTest() 33 | { 34 | EnsureIsConfigured(); 35 | 36 | var userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36"; 37 | 38 | var message = new HttpRequestMessage(); 39 | message.Method = HttpMethod.Get; 40 | message.RequestUri = new Uri("http://httpbin.org/user-agent"); 41 | message.Headers.Add("User-Agent", userAgent); 42 | 43 | var response = await GetResponseMessageAsync(message); 44 | 45 | Assert.NotNull(response); 46 | 47 | var userAgentActual = await GetJsonStringValue(response, "user-agent"); 48 | 49 | Assert.NotEmpty(userAgentActual); 50 | Assert.Equal(userAgent, userAgentActual); 51 | } 52 | 53 | [Fact] 54 | public async Task GetRequestTest() 55 | { 56 | EnsureIsConfigured(); 57 | 58 | var key = "key"; 59 | var value = "value"; 60 | 61 | var message = new HttpRequestMessage(); 62 | message.Method = HttpMethod.Get; 63 | message.RequestUri = new Uri($"http://httpbin.org/get?{key}={value}"); 64 | 65 | var response = await GetResponseMessageAsync(message); 66 | 67 | var actual = await GetJsonDictionaryValue(response, "args"); 68 | 69 | Assert.True(actual.ContainsKey(key)); 70 | Assert.True(actual.ContainsValue(value)); 71 | } 72 | 73 | [Fact] 74 | public async Task GetUtf8Test() 75 | { 76 | EnsureIsConfigured(); 77 | 78 | var excepted = "∮"; 79 | 80 | var message = new HttpRequestMessage(); 81 | message.Method = HttpMethod.Get; 82 | message.RequestUri = new Uri("http://httpbin.org/encoding/utf8"); 83 | 84 | 85 | var response = await GetResponseMessageAsync(message); 86 | var actual = await response.Content.ReadAsStringAsync(); 87 | 88 | Assert.Contains(excepted, actual); 89 | } 90 | 91 | [Fact] 92 | public async Task GetHtmlPageTest() 93 | { 94 | EnsureIsConfigured(); 95 | 96 | long exceptedLength = 3741; 97 | var contentType = "text/html"; 98 | var charSet = "utf-8"; 99 | 100 | var message = new HttpRequestMessage(); 101 | message.Method = HttpMethod.Get; 102 | message.RequestUri = new Uri("http://httpbin.org/html"); 103 | 104 | var response = await GetResponseMessageAsync(message); 105 | 106 | var content = response.Content; 107 | Assert.NotNull(content); 108 | 109 | var headers = response.Content.Headers; 110 | Assert.NotNull(headers); 111 | 112 | Assert.NotNull(headers.ContentLength); 113 | Assert.Equal(exceptedLength, headers.ContentLength.Value); 114 | Assert.NotNull(headers.ContentType); 115 | Assert.Equal(contentType, headers.ContentType.MediaType); 116 | Assert.Equal(charSet, headers.ContentType.CharSet); 117 | } 118 | 119 | [Fact] 120 | public async Task DelayTest() 121 | { 122 | EnsureIsConfigured(); 123 | 124 | var message = new HttpRequestMessage(); 125 | message.Method = HttpMethod.Get; 126 | message.RequestUri = new Uri("http://httpbin.org/delay/4"); 127 | 128 | var response = await GetResponseMessageAsync(message); 129 | var source = response.Content.ReadAsStringAsync(); 130 | 131 | Assert.NotNull(response); 132 | Assert.NotNull(source); 133 | } 134 | 135 | [Fact] 136 | public async Task StreamTest() 137 | { 138 | EnsureIsConfigured(); 139 | 140 | var message = new HttpRequestMessage(); 141 | message.Method = HttpMethod.Get; 142 | message.RequestUri = new Uri("http://httpbin.org/stream/20"); 143 | 144 | var response = await GetResponseMessageAsync(message); 145 | var source = response.Content.ReadAsStringAsync(); 146 | 147 | Assert.NotNull(response); 148 | Assert.NotNull(source); 149 | } 150 | 151 | [Fact] 152 | public async Task GzipTest() 153 | { 154 | EnsureIsConfigured(); 155 | 156 | var excepted = "gzip, deflate"; 157 | 158 | var message = new HttpRequestMessage(); 159 | message.Method = HttpMethod.Get; 160 | message.RequestUri = new Uri("http://httpbin.org/gzip"); 161 | 162 | var response = await GetResponseMessageAsync(message); 163 | var acutal = await GetJsonStringValue(response, "Accept-Encoding"); 164 | 165 | Assert.NotNull(response); 166 | Assert.NotNull(acutal); 167 | Assert.Equal(excepted, acutal); 168 | } 169 | 170 | [Fact] 171 | public async Task CookiesTest() 172 | { 173 | EnsureIsConfigured(); 174 | 175 | HttpResponseMessage response = null; 176 | 177 | var name = "name"; 178 | var value = "value"; 179 | 180 | var message = new HttpRequestMessage(); 181 | message.Method = HttpMethod.Get; 182 | message.RequestUri = new Uri($"http://httpbin.org/cookies/set?{name}={value}"); 183 | 184 | var handler = CreateNewSocks5Client(); 185 | handler.CookieContainer = new System.Net.CookieContainer(); 186 | handler.UseCookies = true; 187 | var client = new HttpClient(handler); 188 | 189 | try 190 | { 191 | response = await client.SendAsync(message); 192 | } 193 | catch (Exception ex) 194 | { 195 | Debug.WriteLine("Exception caught! " + ex.Message); 196 | } 197 | 198 | Assert.NotNull(response); 199 | var cookies = handler.CookieContainer.GetCookies(new Uri("http://httpbin.org/")); 200 | 201 | Assert.Equal(1, cookies.Count); 202 | var cookie = cookies[name]; 203 | Assert.Equal(name, cookie.Name); 204 | Assert.Equal(value, cookie.Value); 205 | 206 | handler.Dispose(); 207 | client.Dispose(); 208 | } 209 | 210 | [Fact] 211 | public async Task StatusCodeTest() 212 | { 213 | EnsureIsConfigured(); 214 | 215 | var code = "404"; 216 | var excepted = "NotFound"; 217 | var message = new HttpRequestMessage(); 218 | message.Method = HttpMethod.Get; 219 | message.RequestUri = new Uri($"http://httpbin.org/status/{code}"); 220 | 221 | var response = await GetResponseMessageAsync(message); 222 | 223 | Assert.NotNull(response); 224 | Assert.Equal(excepted, response.StatusCode.ToString()); 225 | } 226 | 227 | [Fact] 228 | public async Task HttpClient_SendAsync_SetExplicitHostHeader_ShouldNotFail() 229 | { 230 | var message = new HttpRequestMessage(HttpMethod.Get, "https://httpbin.org/headers"); 231 | message.Headers.Host = "httpbin.org"; 232 | 233 | var response = await GetResponseMessageAsync(message).ConfigureAwait(false); 234 | 235 | Assert.NotNull(response); 236 | Assert.Equal(HttpStatusCode.OK, response.StatusCode); 237 | } 238 | 239 | #endregion 240 | 241 | #region Helpers 242 | 243 | private void GatherTestConfiguration() 244 | { 245 | IConfigurationRoot configuration; 246 | 247 | var appConfigMsgWarning = "{0} not configured in proxysettings.json! Some tests may fail."; 248 | 249 | 250 | try 251 | { 252 | configuration = new ConfigurationBuilder() 253 | .AddJsonFile("proxysettings.json") 254 | .Build(); 255 | } 256 | catch (FileNotFoundException) 257 | { 258 | Debug.WriteLine("proxysettings.json not found in project folder"); 259 | return; 260 | } 261 | 262 | proxySettings = new ProxySettings(); 263 | 264 | var host = configuration["host"]; 265 | if (String.IsNullOrEmpty(host)) 266 | { 267 | Debug.WriteLine(String.Format(appConfigMsgWarning, nameof(host))); 268 | } 269 | else 270 | { 271 | proxySettings.Host = host; 272 | } 273 | 274 | var port = configuration["port"]; 275 | if (String.IsNullOrEmpty(port)) 276 | { 277 | Debug.WriteLine(String.Format(appConfigMsgWarning, nameof(port))); 278 | } 279 | else 280 | { 281 | proxySettings.Port = Int32.Parse(port); 282 | } 283 | 284 | //TODO: Setup manualy 285 | var username = configuration["username"]; 286 | var password = configuration["password"]; 287 | } 288 | 289 | private ProxyClientHandler CreateNewSocks5Client() 290 | { 291 | return new ProxyClientHandler(proxySettings); 292 | } 293 | 294 | private async Task GetJsonStringValue(HttpResponseMessage response, string valueName) 295 | { 296 | JToken token; 297 | var source = await response.Content.ReadAsStringAsync(); 298 | var obj = JObject.Parse(source); 299 | 300 | var result = obj.TryGetValue(valueName, out token); 301 | 302 | if (!result) 303 | { 304 | return String.Empty; 305 | } 306 | 307 | return token.Value(); 308 | } 309 | 310 | private async Task> GetJsonDictionaryValue(HttpResponseMessage response, string valueName) 311 | { 312 | JToken token; 313 | var source = await response.Content.ReadAsStringAsync(); 314 | var obj = JObject.Parse(source); 315 | 316 | var result = obj.TryGetValue(valueName, out token); 317 | 318 | if (!result) 319 | { 320 | return null; 321 | } 322 | 323 | return token.ToObject>(); 324 | } 325 | 326 | private async Task GetResponseMessageAsync(HttpRequestMessage requestMessage) 327 | { 328 | HttpResponseMessage response = null; 329 | 330 | var handler = CreateNewSocks5Client(); 331 | var client = new HttpClient(handler); 332 | 333 | try 334 | { 335 | response = await client.SendAsync(requestMessage); 336 | } 337 | catch (Exception ex) 338 | { 339 | Debug.WriteLine("Exception caught! " + ex.Message); 340 | } 341 | 342 | handler.Dispose(); 343 | client.Dispose(); 344 | 345 | return response; 346 | } 347 | 348 | private void EnsureIsConfigured() 349 | { 350 | if (proxySettings == null 351 | || proxySettings.Host == null 352 | || proxySettings.Port == 0) 353 | { 354 | throw new Exception("Please add your proxy settings to proxysettings.json in build folder"); 355 | } 356 | } 357 | 358 | #endregion 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /tests/SocksSharp.Tests/SocksSharp.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | PreserveNewest 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/SocksSharp.Tests/proxysettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "127.0.0.1", 3 | "port": "9050", 4 | "username": "", 5 | "password": "" 6 | } 7 | --------------------------------------------------------------------------------