提交Unity 联机Pro

This commit is contained in:
PC-20230316NUNE\Administrator
2024-08-17 14:27:18 +08:00
parent f00193b000
commit 894100ae37
7448 changed files with 854473 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 855ac4bfd19c3a4409869bf1ac3f6b55
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,65 @@
#if !BESTHTTP_DISABLE_PROXY && UNITY_ANDROID && !UNITY_EDITOR
using System;
using UnityEngine;
namespace BestHTTP.Proxies.Autodetect
{
public sealed class AndroidProxyDetector : IProxyDetector
{
private const string ClassPath = "com.besthttp.proxy.ProxyFinder";
Proxy IProxyDetector.GetProxy(HTTPRequest request)
{
try
{
var proxyUrl = FindFor(request.CurrentUri.ToString());
HTTPManager.Logger.Information(nameof(AndroidProxyDetector), $"{nameof(IProxyDetector.GetProxy)} - FindFor returned with proxyUrl: '{proxyUrl}'", request.Context);
if (proxyUrl == null)
return null;
if (proxyUrl.StartsWith("socks://", StringComparison.OrdinalIgnoreCase))
{
return new SOCKSProxy(new Uri(proxyUrl), null);
}
else if (proxyUrl.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
{
return new HTTPProxy(new Uri(proxyUrl));
}
else
{
HTTPManager.Logger.Warning(nameof(AndroidProxyDetector), $"{nameof(IProxyDetector.GetProxy)} - FindFor returned with unknown format. proxyUrl: '{proxyUrl}'", request.Context);
}
}
catch (Exception ex)
{
HTTPManager.Logger.Exception(nameof(AndroidProxyDetector), nameof(IProxyDetector.GetProxy), ex, request.Context);
}
return null;
}
private string FindFor(string uriStr) => Call<string>("FindFor", uriStr);
private static T Call<T>(string methodName, params object[] args)
{
bool isMainThread = HTTPUpdateDelegator.Instance.IsMainThread();
try
{
if (!isMainThread)
AndroidJNI.AttachCurrentThread();
using (var javaClass = new AndroidJavaClass(ClassPath))
return javaClass.CallStatic<T>(methodName, args);
}
finally
{
if (!isMainThread)
AndroidJNI.DetachCurrentThread();
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ac7faed5a1e9336479173f34a38a8965
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,80 @@
#if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
using System;
using System.Linq;
using BestHTTP.Connections;
// Examples on proxy strings:
// https://gist.github.com/yougg/5d2b3353fc5e197a0917aae0b3287d64
namespace BestHTTP.Proxies.Autodetect
{
/// <summary>
/// Based on https://curl.se/docs/manual.html "Environment Variables" section.
/// </summary>
public sealed class EnvironmentProxyDetector : IProxyDetector
{
private Proxy _cachedProxy;
Proxy IProxyDetector.GetProxy(HTTPRequest request)
{
if (this._cachedProxy != null)
return this._cachedProxy;
string proxyUrl = null;
if (HTTPProtocolFactory.IsSecureProtocol(request.CurrentUri))
{
proxyUrl = GetEnv("HTTPS_PROXY");
HTTPManager.Logger.Information(nameof(EnvironmentProxyDetector), $"{nameof(IProxyDetector.GetProxy)} - HTTPS_PROXY: '{proxyUrl}'", request.Context);
}
else
{
proxyUrl = GetEnv("HTTP_PROXY");
HTTPManager.Logger.Information(nameof(EnvironmentProxyDetector), $"{nameof(IProxyDetector.GetProxy)} - HTTP_PROXY: '{proxyUrl}'", request.Context);
}
if (proxyUrl == null)
{
proxyUrl = GetEnv("ALL_PROXY");
}
else
HTTPManager.Logger.Information(nameof(EnvironmentProxyDetector), $"{nameof(IProxyDetector.GetProxy)} - ALL_PROXY: '{proxyUrl}'", request.Context);
if (string.IsNullOrEmpty(proxyUrl))
return null;
// if the url is just a host[:port], add the http:// part too. Checking for :// should keep and treat the socks:// scheme too.
if (proxyUrl.IndexOf("://") == -1 && !proxyUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
proxyUrl = "http://" + proxyUrl;
string exceptionList = null;
try
{
var proxyUri = new Uri(proxyUrl);
Proxy proxy = null;
if (proxyUri.Scheme.StartsWith("socks", StringComparison.OrdinalIgnoreCase))
proxy = new SOCKSProxy(proxyUri, null);
else
proxy = new HTTPProxy(proxyUri);
// A comma-separated list of host names that should not go through any proxy is set in (only an asterisk, * matches all hosts)
exceptionList = GetEnv("NO_PROXY");
if (!string.IsNullOrEmpty(exceptionList))
proxy.Exceptions = exceptionList.Split(';').ToList<string>();
return this._cachedProxy = proxy;
}
catch (Exception ex)
{
HTTPManager.Logger.Exception(nameof(EnvironmentProxyDetector), $"GetProxy - proxyUrl: '{proxyUrl}', exceptionList: '{exceptionList}'", ex, request.Context);
}
return null;
}
string GetEnv(string key) => System.Environment.GetEnvironmentVariable(key) ?? System.Environment.GetEnvironmentVariable(key.ToLowerInvariant());
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 152166acb3a569d45a58d0fa2b33eec3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,62 @@
#if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
using System;
namespace BestHTTP.Proxies.Autodetect
{
/// <summary>
/// This is a detector using the .net framework's implementation. It might work not just under Windows but MacOS and Linux too.
/// </summary>
/// <see cref="https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.defaultproxy?view=net-6.0"/>
public sealed class FrameworkProxyDetector : IProxyDetector
{
Proxy IProxyDetector.GetProxy(HTTPRequest request)
{
var detectedProxy = System.Net.WebRequest.GetSystemWebProxy() as System.Net.WebProxy;
if (detectedProxy != null && detectedProxy.Address != null)
{
var proxyUri = detectedProxy.GetProxy(request.CurrentUri);
if (proxyUri != null && !proxyUri.Equals(request.CurrentUri))
{
if (proxyUri.Scheme.StartsWith("socks", StringComparison.OrdinalIgnoreCase))
{
return SetExceptionList(new SOCKSProxy(proxyUri, null), detectedProxy);
}
else if (proxyUri.Scheme.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
return SetExceptionList(new HTTPProxy(proxyUri), detectedProxy);
}
else
{
HTTPManager.Logger.Warning(nameof(FrameworkProxyDetector), $"{nameof(IProxyDetector.GetProxy)} - FindFor returned with unknown format. proxyUri: '{proxyUri}'", request.Context);
}
}
}
return null;
}
private Proxy SetExceptionList(Proxy proxy, System.Net.WebProxy detectedProxy)
{
if (detectedProxy.BypassProxyOnLocal)
{
proxy.Exceptions = proxy.Exceptions ?? new System.Collections.Generic.List<string>();
proxy.Exceptions.Add("localhost");
proxy.Exceptions.Add("127.0.0.1");
}
// TODO: use BypassList to put more entries to the Exceptions list.
// But because BypassList contains regex strings, we either
// 1.) store and use regex strings in the Exception list (not backward compatible)
// 2.) store non-regex strings but create a new list for regex
// 3.) detect if the stored entry in the Exceptions list is regex or not and use it accordingly
// "^.*\\.httpbin\\.org$"
// https://github.com/Benedicht/BestHTTP-Issues/issues/141
return proxy;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 58ef74f62e307544fa7e434332a6d3ac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,14 @@
#if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
namespace BestHTTP.Proxies.Autodetect
{
/// <summary>
/// This one just returns with HTTPManager.Proxy,
/// so when ProgrammaticallyAddedProxyDetector is used in the first place for the ProxyDetector,
/// HTTPManager.Proxy gets the highest priority.
/// </summary>
public sealed class ProgrammaticallyAddedProxyDetector : IProxyDetector
{
Proxy IProxyDetector.GetProxy(HTTPRequest request) => HTTPManager.Proxy;
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ad0e404074491274e83de0ce83db8a97
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,145 @@
#if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
using System;
using BestHTTP.Core;
namespace BestHTTP.Proxies.Autodetect
{
public interface IProxyDetector
{
Proxy GetProxy(HTTPRequest request);
}
public enum ProxyDetectionMode
{
/// <summary>
/// In Continouos mode the ProxyDetector will check for a proxy for every request.
/// </summary>
Continouos,
/// <summary>
/// This mode will cache the first Proxy found and use it for consecutive requests.
/// </summary>
CacheFirstFound
}
public sealed class ProxyDetector
{
public static IProxyDetector[] GetDefaultDetectors() => new IProxyDetector[] {
// HTTPManager.Proxy has the highest priority
new ProgrammaticallyAddedProxyDetector(),
// then comes the environment set
new EnvironmentProxyDetector(),
// .net framework's detector
new FrameworkProxyDetector(),
#if UNITY_ANDROID && !UNITY_EDITOR
new AndroidProxyDetector(),
#endif
};
private IProxyDetector[] _proxyDetectors;
private ProxyDetectionMode _detectionMode;
private bool _attached;
public ProxyDetector()
: this(ProxyDetectionMode.CacheFirstFound, GetDefaultDetectors())
{ }
public ProxyDetector(ProxyDetectionMode detectionMode)
:this(detectionMode, GetDefaultDetectors())
{ }
public ProxyDetector(ProxyDetectionMode detectionMode, IProxyDetector[] proxyDetectors)
{
this._detectionMode = detectionMode;
this._proxyDetectors = proxyDetectors;
if (this._proxyDetectors != null)
Reattach();
}
public void Reattach()
{
HTTPManager.Logger.Information(nameof(ProxyDetector), $"{nameof(Reattach)}({this._attached})");
if (!this._attached)
{
RequestEventHelper.OnEvent += OnRequestEvent;
this._attached = true;
}
}
/// <summary>
/// Call Detach() to disable ProxyDetector's logic to find and set a proxy.
/// </summary>
public void Detach()
{
HTTPManager.Logger.Information(nameof(ProxyDetector), $"{nameof(Detach)}({this._attached})");
if (this._attached)
{
RequestEventHelper.OnEvent -= OnRequestEvent;
this._attached = false;
}
}
private void OnRequestEvent(RequestEventInfo @event)
{
// The Resend event is raised for every request when it's queued up (sent or redirected).
if (@event.Event == RequestEvents.Resend && @event.SourceRequest.Proxy == null)
{
Uri uri = @event.SourceRequest.CurrentUri;
HTTPManager.Logger.Information(nameof(ProxyDetector), $"OnRequestEvent(RequestEvents.Resend) uri: '{uri}'", @event.SourceRequest.Context);
try
{
foreach (var detector in this._proxyDetectors)
{
if (detector == null)
continue;
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
HTTPManager.Logger.Verbose(nameof(ProxyDetector), $"Calling {detector.GetType().Name}'s GetProxy", @event.SourceRequest.Context);
var proxy = detector.GetProxy(@event.SourceRequest);
if (proxy != null && proxy.UseProxyForAddress(uri))
{
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
HTTPManager.Logger.Verbose(nameof(ProxyDetector), $"[{detector.GetType().Name}] Proxy found: {proxy.Address} ", @event.SourceRequest.Context);
switch (this._detectionMode)
{
case ProxyDetectionMode.Continouos:
@event.SourceRequest.Proxy = proxy;
break;
case ProxyDetectionMode.CacheFirstFound:
HTTPManager.Proxy = @event.SourceRequest.Proxy = proxy;
HTTPManager.Logger.Verbose(nameof(ProxyDetector), $"Proxy cached in HTTPManager.Proxy!", @event.SourceRequest.Context);
Detach();
break;
}
return;
}
}
HTTPManager.Logger.Information(nameof(ProxyDetector), $"No Proxy for '{uri}'.", @event.SourceRequest.Context);
}
catch (Exception ex)
{
if (HTTPManager.Logger.Level == BestHTTP.Logger.Loglevels.All)
HTTPManager.Logger.Exception(nameof(ProxyDetector), $"GetProxyFor({@event.SourceRequest.CurrentUri})", ex, @event.SourceRequest.Context);
}
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f11e35f15ae944141a7fe661e2783ae4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,290 @@
#if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using BestHTTP.Authentication;
using BestHTTP.Connections;
using BestHTTP.Extensions;
using BestHTTP.PlatformSupport.Memory;
namespace BestHTTP
{
public abstract class Proxy
{
/// <summary>
/// Address of the proxy server. It has to be in the http://proxyaddress:port form.
/// </summary>
public Uri Address { get; set; }
/// <summary>
/// Credentials of the proxy
/// </summary>
public Credentials Credentials { get; set; }
/// <summary>
/// Use the proxy except for addresses that start with these entries. Elements of this list are compared to the Host (DNS or IP address) part of the uri.
/// </summary>
public List<string> Exceptions { get; set; }
internal Proxy(Uri address, Credentials credentials)
{
this.Address = address;
this.Credentials = credentials;
}
internal abstract void Connect(Stream stream, HTTPRequest request);
internal abstract string GetRequestPath(Uri uri);
internal abstract bool SetupRequest(HTTPRequest request);
internal bool UseProxyForAddress(Uri address)
{
if (this.Exceptions == null)
return true;
string host = address.Host;
// https://github.com/httplib2/httplib2/issues/94
// If domain starts with a dot (example: .example.com):
// 1. Use endswith to match any subdomain (foo.example.com should match)
// 2. Remove the dot and do an exact match (example.com should also match)
//
// If domain does not start with a dot (example: example.com):
// 1. It should be an exact match.
for (int i = 0; i < this.Exceptions.Count; ++i)
{
var exception = this.Exceptions[i];
if (exception == "*")
return false;
if (exception.StartsWith("."))
{
// Use EndsWith to match any subdomain
if (host.EndsWith(exception))
return false;
// Remove the dot and
exception = exception.Substring(1);
}
// do an exact match
if (host.Equals(exception))
return false;
}
return true;
}
}
public sealed class HTTPProxy : Proxy
{
/// <summary>
/// True if the proxy can act as a transparent proxy
/// </summary>
public bool IsTransparent { get; set; }
/// <summary>
/// Some non-transparent proxies are except only the path and query of the request uri. Default value is true
/// </summary>
public bool SendWholeUri { get; set; }
/// <summary>
/// Regardless of the value of IsTransparent, for secure protocols(HTTPS://, WSS://) the plugin will use the proxy as an explicit proxy(will issue a CONNECT request to the proxy)
/// </summary>
public bool NonTransparentForHTTPS { get; set; }
public HTTPProxy(Uri address)
:this(address, null, false)
{}
public HTTPProxy(Uri address, Credentials credentials)
:this(address, credentials, false)
{}
public HTTPProxy(Uri address, Credentials credentials, bool isTransparent)
:this(address, credentials, isTransparent, true)
{ }
public HTTPProxy(Uri address, Credentials credentials, bool isTransparent, bool sendWholeUri)
: this(address, credentials, isTransparent, sendWholeUri, true)
{ }
public HTTPProxy(Uri address, Credentials credentials, bool isTransparent, bool sendWholeUri, bool nonTransparentForHTTPS)
:base(address, credentials)
{
this.IsTransparent = isTransparent;
this.SendWholeUri = sendWholeUri;
this.NonTransparentForHTTPS = nonTransparentForHTTPS;
}
internal override string GetRequestPath(Uri uri)
{
return this.SendWholeUri ? uri.OriginalString : uri.GetRequestPathAndQueryURL();
}
internal override bool SetupRequest(HTTPRequest request)
{
if (request == null || request.Response == null || !this.IsTransparent)
return false;
string authHeader = DigestStore.FindBest(request.Response.GetHeaderValues("proxy-authenticate"));
if (!string.IsNullOrEmpty(authHeader))
{
var digest = DigestStore.GetOrCreate(request.Proxy.Address);
digest.ParseChallange(authHeader);
if (request.Proxy.Credentials != null && digest.IsUriProtected(request.Proxy.Address) && (!request.HasHeader("Proxy-Authorization") || digest.Stale))
{
switch (request.Proxy.Credentials.Type)
{
case AuthenticationTypes.Basic:
// With Basic authentication we don't want to wait for a challenge, we will send the hash with the first request
request.SetHeader("Proxy-Authorization", string.Concat("Basic ", Convert.ToBase64String(Encoding.UTF8.GetBytes(request.Proxy.Credentials.UserName + ":" + request.Proxy.Credentials.Password))));
return true;
case AuthenticationTypes.Unknown:
case AuthenticationTypes.Digest:
//var digest = DigestStore.Get(request.Proxy.Address);
if (digest != null)
{
string authentication = digest.GenerateResponseHeader(request, request.Proxy.Credentials, true);
if (!string.IsNullOrEmpty(authentication))
{
request.SetHeader("Proxy-Authorization", authentication);
return true;
}
}
break;
}
}
}
return false;
}
internal override void Connect(Stream stream, HTTPRequest request)
{
bool isSecure = HTTPProtocolFactory.IsSecureProtocol(request.CurrentUri);
if (!this.IsTransparent || (isSecure && this.NonTransparentForHTTPS))
{
using (var bufferedStream = new WriteOnlyBufferedStream(stream, HTTPRequest.UploadChunkSize))
using (var outStream = new BinaryWriter(bufferedStream, Encoding.UTF8))
{
bool retry;
do
{
// If we have to because of a authentication request, we will switch it to true
retry = false;
string connectStr = string.Format("CONNECT {0}:{1} HTTP/1.1", request.CurrentUri.Host, request.CurrentUri.Port.ToString());
HTTPManager.Logger.Information("HTTPProxy", "Sending " + connectStr, request.Context);
outStream.SendAsASCII(connectStr);
outStream.Write(HTTPRequest.EOL);
outStream.SendAsASCII("Proxy-Connection: Keep-Alive");
outStream.Write(HTTPRequest.EOL);
outStream.SendAsASCII("Connection: Keep-Alive");
outStream.Write(HTTPRequest.EOL);
outStream.SendAsASCII(string.Format("Host: {0}:{1}", request.CurrentUri.Host, request.CurrentUri.Port.ToString()));
outStream.Write(HTTPRequest.EOL);
// Proxy Authentication
if (this.Credentials != null)
{
switch (this.Credentials.Type)
{
case AuthenticationTypes.Basic:
{
// With Basic authentication we don't want to wait for a challenge, we will send the hash with the first request
var buff = string.Format("Proxy-Authorization: {0}", string.Concat("Basic ", Convert.ToBase64String(Encoding.UTF8.GetBytes(this.Credentials.UserName + ":" + this.Credentials.Password)))).GetASCIIBytes();
outStream.Write(buff.Data, buff.Offset, buff.Count);
BufferPool.Release(buff);
outStream.Write(HTTPRequest.EOL);
break;
}
case AuthenticationTypes.Unknown:
case AuthenticationTypes.Digest:
{
var digest = DigestStore.Get(this.Address);
if (digest != null)
{
string authentication = digest.GenerateResponseHeader(request, this.Credentials, true);
if (!string.IsNullOrEmpty(authentication))
{
string auth = string.Format("Proxy-Authorization: {0}", authentication);
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
HTTPManager.Logger.Information("HTTPProxy", "Sending proxy authorization header: " + auth, request.Context);
var buff = auth.GetASCIIBytes();
outStream.Write(buff.Data, buff.Offset, buff.Count);
BufferPool.Release(buff);
outStream.Write(HTTPRequest.EOL);
}
}
break;
}
}
}
outStream.Write(HTTPRequest.EOL);
// Make sure to send all the wrote data to the wire
outStream.Flush();
request.ProxyResponse = new HTTPResponse(request, stream, false, false, true);
// Read back the response of the proxy
if (!request.ProxyResponse.Receive(-1, true))
throw new Exception("Connection to the Proxy Server failed!");
if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
HTTPManager.Logger.Information("HTTPProxy", "Proxy returned - status code: " + request.ProxyResponse.StatusCode + " message: " + request.ProxyResponse.Message + " Body: " + request.ProxyResponse.DataAsText, request.Context);
switch (request.ProxyResponse.StatusCode)
{
// Proxy authentication required
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.8
case 407:
{
string authHeader = DigestStore.FindBest(request.ProxyResponse.GetHeaderValues("proxy-authenticate"));
if (!string.IsNullOrEmpty(authHeader))
{
var digest = DigestStore.GetOrCreate(this.Address);
digest.ParseChallange(authHeader);
if (this.Credentials != null && digest.IsUriProtected(this.Address) && (!request.HasHeader("Proxy-Authorization") || digest.Stale))
retry = true;
}
if (!retry)
throw new Exception(string.Format("Can't authenticate Proxy! Status Code: \"{0}\", Message: \"{1}\" and Response: {2}", request.ProxyResponse.StatusCode, request.ProxyResponse.Message, request.ProxyResponse.DataAsText));
break;
}
default:
if (!request.ProxyResponse.IsSuccess)
throw new Exception(string.Format("Proxy returned Status Code: \"{0}\", Message: \"{1}\" and Response: {2}", request.ProxyResponse.StatusCode, request.ProxyResponse.Message, request.ProxyResponse.DataAsText));
break;
}
} while (retry);
} // using outstream
}
}
}
}
#endif

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 9e3868a6f38c2cc42bbb8c562caba79a
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,371 @@
#if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
using System;
using System.IO;
using System.Text;
using BestHTTP.Authentication;
using BestHTTP.Extensions;
using BestHTTP.PlatformSupport.Memory;
using BestHTTP.PlatformSupport.Text;
namespace BestHTTP
{
internal enum SOCKSVersions : byte
{
Unknown = 0x00,
V5 = 0x05
}
/// <summary>
/// https://tools.ietf.org/html/rfc1928
/// The values currently defined for METHOD are:
/// o X'00' NO AUTHENTICATION REQUIRED
/// o X'01' GSSAPI
/// o X'02' USERNAME/PASSWORD
/// o X'03' to X'7F' IANA ASSIGNED
/// o X'80' to X'FE' RESERVED FOR PRIVATE METHODS
/// o X'FF' NO ACCEPTABLE METHODS
/// </summary>
internal enum SOCKSMethods : byte
{
NoAuthenticationRequired = 0x00,
GSSAPI = 0x01,
UsernameAndPassword = 0x02,
NoAcceptableMethods = 0xFF
}
internal enum SOCKSReplies : byte
{
Succeeded = 0x00,
GeneralSOCKSServerFailure = 0x01,
ConnectionNotAllowedByRuleset = 0x02,
NetworkUnreachable = 0x03,
HostUnreachable = 0x04,
ConnectionRefused = 0x05,
TTLExpired = 0x06,
CommandNotSupported = 0x07,
AddressTypeNotSupported = 0x08
}
internal enum SOCKSAddressTypes
{
IPV4 = 0x00,
DomainName = 0x03,
IPv6 = 0x04
}
public sealed class SOCKSProxy : Proxy
{
public SOCKSProxy(Uri address, Credentials credentials)
: base(address, credentials)
{ }
internal override string GetRequestPath(Uri uri)
{
return uri.GetRequestPathAndQueryURL();
}
internal override bool SetupRequest(HTTPRequest request)
{
return false;
}
internal override void Connect(Stream stream, HTTPRequest request)
{
var buffer = BufferPool.Get(1024, true);
try
{
int count = 0;
// https://tools.ietf.org/html/rfc1928
// The client connects to the server, and sends a version
// identifier/method selection message:
//
// +----+----------+----------+
// |VER | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+
//
// The VER field is set to X'05' for this version of the protocol. The
// NMETHODS field contains the number of method identifier octets that
// appear in the METHODS field.
//
buffer[count++] = (byte)SOCKSVersions.V5;
if (this.Credentials != null)
{
buffer[count++] = 0x02; // method count
buffer[count++] = (byte)SOCKSMethods.UsernameAndPassword;
buffer[count++] = (byte)SOCKSMethods.NoAuthenticationRequired;
}
else
{
buffer[count++] = 0x01; // method count
buffer[count++] = (byte)SOCKSMethods.NoAuthenticationRequired;
}
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
HTTPManager.Logger.Information("SOCKSProxy", string.Format("Sending method negotiation - count: {0} buffer: {1} ", count.ToString(), BufferToHexStr(buffer, count)), request.Context);
// Write negotiation
stream.Write(buffer, 0, count);
// Read result
count = stream.Read(buffer, 0, buffer.Length);
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
HTTPManager.Logger.Information("SOCKSProxy", string.Format("Negotiation response - count: {0} buffer: {1} ", count.ToString(), BufferToHexStr(buffer, count)), request.Context);
// The server selects from one of the methods given in METHODS, and
// sends a METHOD selection message:
//
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
//
// If the selected METHOD is X'FF', none of the methods listed by the
// client are acceptable, and the client MUST close the connection.
//
// The values currently defined for METHOD are:
//
// o X'00' NO AUTHENTICATION REQUIRED
// o X'01' GSSAPI
// o X'02' USERNAME/PASSWORD
// o X'03' to X'7F' IANA ASSIGNED
// o X'80' to X'FE' RESERVED FOR PRIVATE METHODS
// o X'FF' NO ACCEPTABLE METHODS
//
// The client and server then enter a method-specific sub-negotiation.
SOCKSVersions version = (SOCKSVersions)buffer[0];
SOCKSMethods method = (SOCKSMethods)buffer[1];
// Expected result:
// 1.) Received bytes' count is 2: version + preferred method
// 2.) Version must be 5
// 3.) Preferred method must NOT be 0xFF
if (count != 2)
throw new Exception(string.Format("SOCKS Proxy - Expected read count: 2! count: {0} buffer: {1}" + count.ToString(), BufferToHexStr(buffer, count)));
else if (version != SOCKSVersions.V5)
throw new Exception("SOCKS Proxy - Expected version: 5, received version: " + buffer[0].ToString("X2"));
else if (method == SOCKSMethods.NoAcceptableMethods)
throw new Exception("SOCKS Proxy - Received 'NO ACCEPTABLE METHODS' (0xFF)");
else
{
HTTPManager.Logger.Information("SOCKSProxy", "Method negotiation over. Method: " + method.ToString(), request.Context);
switch (method)
{
case SOCKSMethods.NoAuthenticationRequired:
// nothing to do
break;
case SOCKSMethods.UsernameAndPassword:
if (this.Credentials.UserName.Length > 255)
throw new Exception(string.Format("SOCKS Proxy - Credentials.UserName too long! {0} > 255", this.Credentials.UserName.Length.ToString()));
if (this.Credentials.Password.Length > 255)
throw new Exception(string.Format("SOCKS Proxy - Credentials.Password too long! {0} > 255", this.Credentials.Password.Length.ToString()));
// https://tools.ietf.org/html/rfc1929 : Username/Password Authentication for SOCKS V5
// Once the SOCKS V5 server has started, and the client has selected the
// Username/Password Authentication protocol, the Username/Password
// subnegotiation begins. This begins with the client producing a
// Username/Password request:
//
// +----+------+----------+------+----------+
// |VER | ULEN | UNAME | PLEN | PASSWD |
// +----+------+----------+------+----------+
// | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
// +----+------+----------+------+----------+
HTTPManager.Logger.Information("SOCKSProxy", "starting sub-negotiation", request.Context);
count = 0;
buffer[count++] = 0x01; // version of sub negotiation
WriteString(buffer, ref count, this.Credentials.UserName);
WriteString(buffer, ref count, this.Credentials.Password);
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
HTTPManager.Logger.Information("SOCKSProxy", string.Format("Sending username and password sub-negotiation - count: {0} buffer: {1} ", count.ToString(), BufferToHexStr(buffer, count)), request.Context);
// Write negotiation
stream.Write(buffer, 0, count);
// Read result
count = stream.Read(buffer, 0, buffer.Length);
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
HTTPManager.Logger.Information("SOCKSProxy", string.Format("Username and password sub-negotiation response - count: {0} buffer: {1} ", count.ToString(), BufferToHexStr(buffer, count)), request.Context);
// The server verifies the supplied UNAME and PASSWD, and sends the
// following response:
//
// +----+--------+
// |VER | STATUS |
// +----+--------+
// | 1 | 1 |
// +----+--------+
// A STATUS field of X'00' indicates success. If the server returns a
// `failure' (STATUS value other than X'00') status, it MUST close the
// connection.
bool success = buffer[1] == 0;
if (count != 2)
throw new Exception(string.Format("SOCKS Proxy - Expected read count: 2! count: {0} buffer: {1}" + count.ToString(), BufferToHexStr(buffer, count)));
else if (!success)
throw new Exception("SOCKS proxy: username+password authentication failed!");
HTTPManager.Logger.Information("SOCKSProxy", "Authenticated!", request.Context);
break;
case SOCKSMethods.GSSAPI:
throw new Exception("SOCKS proxy: GSSAPI not supported!");
case SOCKSMethods.NoAcceptableMethods:
throw new Exception("SOCKS proxy: No acceptable method");
}
}
// The SOCKS request is formed as follows:
//
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// Where:
//
// o VER protocol version: X'05'
// o CMD
// o CONNECT X'01'
// o BIND X'02'
// o UDP ASSOCIATE X'03'
// o RSV RESERVED
// o ATYP address type of following address
// o IP V4 address: X'01'
// o DOMAINNAME: X'03'
// o IP V6 address: X'04'
// o DST.ADDR desired destination address
// o DST.PORT desired destination port in network octet
// order
count = 0;
buffer[count++] = (byte)SOCKSVersions.V5; // version: 5
buffer[count++] = 0x01; // command: connect
buffer[count++] = 0x00; // reserved, bust be 0x00
if (request.CurrentUri.IsHostIsAnIPAddress())
{
bool isIPV4 = Extensions.Extensions.IsIpV4AddressValid(request.CurrentUri.Host);
buffer[count++] = isIPV4 ? (byte)SOCKSAddressTypes.IPV4 : (byte)SOCKSAddressTypes.IPv6;
var ipAddress = System.Net.IPAddress.Parse(request.CurrentUri.Host);
var ipBytes = ipAddress.GetAddressBytes();
WriteBytes(buffer, ref count, ipBytes); // destination address
}
else
{
buffer[count++] = (byte)SOCKSAddressTypes.DomainName;
// The first octet of the address field contains the number of octets of name that
// follow, there is no terminating NUL octet.
WriteString(buffer, ref count, request.CurrentUri.Host);
}
// destination port in network octet order
buffer[count++] = (byte)((request.CurrentUri.Port >> 8) & 0xFF);
buffer[count++] = (byte)(request.CurrentUri.Port & 0xFF);
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
HTTPManager.Logger.Information("SOCKSProxy", string.Format("Sending connect request - count: {0} buffer: {1} ", count.ToString(), BufferToHexStr(buffer, count)), request.Context);
stream.Write(buffer, 0, count);
count = stream.Read(buffer, 0, buffer.Length);
if (HTTPManager.Logger.Level == Logger.Loglevels.All)
HTTPManager.Logger.Information("SOCKSProxy", string.Format("Connect response - count: {0} buffer: {1} ", count.ToString(), BufferToHexStr(buffer, count)), request.Context);
// The SOCKS request information is sent by the client as soon as it has
// established a connection to the SOCKS server, and completed the
// authentication negotiations. The server evaluates the request, and
// returns a reply formed as follows:
//
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// Where:
// o VER protocol version: X'05'
// o REP Reply field:
// o X'00' succeeded
// o X'01' general SOCKS server failure
// o X'02' connection not allowed by ruleset
// o X'03' Network unreachable
// o X'04' Host unreachable
// o X'05' Connection refused
// o X'06' TTL expired
// o X'07' Command not supported
// o X'08' Address type not supported
// o X'09' to X'FF' unassigned
// o RSV RESERVED
// o ATYP address type of following address
// o IP V4 address: X'01'
// o DOMAINNAME: X'03'
// o IP V6 address: X'04'
// o BND.ADDR server bound address
// o BND.PORT server bound port in network octet order
//
// Fields marked RESERVED (RSV) must be set to X'00'.
version = (SOCKSVersions)buffer[0];
SOCKSReplies reply = (SOCKSReplies)buffer[1];
// at least 10 bytes expected as a result
if (count < 10)
throw new Exception(string.Format("SOCKS proxy: not enough data returned by the server. Expected count is at least 10 bytes, server returned {0} bytes! content: {1}", count.ToString(), BufferToHexStr(buffer, count)));
else if (reply != SOCKSReplies.Succeeded)
throw new Exception("SOCKS proxy error: " + reply.ToString());
HTTPManager.Logger.Information("SOCKSProxy", "Connected!", request.Context);
}
finally
{
BufferPool.Release(buffer);
}
}
private void WriteString(byte[] buffer, ref int count, string str)
{
// Get the bytes
int byteCount = Encoding.UTF8.GetByteCount(str);
if (byteCount > 255)
throw new Exception(string.Format("SOCKS Proxy - String is too large ({0}) to fit in 255 bytes!", byteCount.ToString()));
// number of bytes
buffer[count++] = (byte)byteCount;
// and the bytes itself
Encoding.UTF8.GetBytes(str, 0, str.Length, buffer, count);
count += byteCount;
}
private void WriteBytes(byte[] buffer, ref int count, byte[] bytes)
{
Array.Copy(bytes, 0, buffer, count, bytes.Length);
count += bytes.Length;
}
private string BufferToHexStr(byte[] buffer, int count)
{
StringBuilder sb = StringBuilderPool.Get(count * 2); //new StringBuilder(count * 2);
for (int i = 0; i < count; ++i)
sb.AppendFormat("0x{0} ", buffer[i].ToString("X2"));
return StringBuilderPool.ReleaseAndGrab(sb);
}
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: c9df92482bea23b4aab51dbfc2522c63
timeCreated: 1548063275
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: