Files
ichni_Official/Packages/com.tivadar.best.http/Runtime/HTTP/Proxies/Proxy.cs
2026-06-15 18:18:16 +08:00

185 lines
7.6 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading;
using Best.HTTP.Request.Authentication;
using Best.HTTP.Shared.Logger;
using Best.HTTP.Shared.Streams;
namespace Best.HTTP.Proxies
{
/// <summary>
/// Represents parameters used when connecting through a proxy server.
/// </summary>
/// <remarks>
/// The ProxyConnectParameters struct defines the parameters required when initiating a connection
/// through a proxy server. It includes information about the proxy, target URI, and callbacks for success and error handling.
/// This struct is commonly used during the negotiation steps in the <see cref="Best.HTTP.Shared.PlatformSupport.Network.Tcp.Negotiator"/> class.
/// </remarks>
public struct ProxyConnectParameters
{
/// <summary>
/// The maximum number of authentication attempts allowed during proxy connection.
/// </summary>
public const int MaxAuthenticationAttempts = 1;
/// <summary>
/// The proxy server through which the connection is established.
/// </summary>
public Proxy proxy;
/// <summary>
/// The stream used for communication with the proxy server.
/// </summary>
public PeekableContentProviderStream stream;
/// <summary>
/// The target URI to reach through the proxy server.
/// </summary>
public Uri uri;
/// <summary>
/// A cancellation token that allows canceling the proxy connection operation.
/// </summary>
public CancellationToken token;
/// <summary>
/// The number of authentication attempts made during proxy connection.
/// </summary>
public int AuthenticationAttempts;
/// <summary>
/// Gets or sets a value indicating whether to create a proxy tunnel.
/// </summary>
/// <remarks>
/// A proxy tunnel, also known as a TCP tunnel, is established when communication between the client and the target server
/// needs to be relayed through the proxy without modification. Setting this field to <c>true</c> indicates the intention
/// to create a tunnel, allowing the data to pass through the proxy without interpretation or alteration by the proxy.
/// This is typically used for protocols like HTTPS, where end-to-end encryption is desired, and the proxy should act as a
/// pass-through conduit.
/// </remarks>
public bool createTunel;
/// <summary>
/// The logging context for debugging purposes.
/// </summary>
public LoggingContext context;
/// <summary>
/// A callback to be executed upon successful proxy connection.
/// </summary>
public Action<ProxyConnectParameters> OnSuccess;
/// <summary>
/// A callback to be executed upon encountering an error during proxy connection.
/// </summary>
/// <remarks>
/// The callback includes parameters for the current connection parameters, the encountered exception,
/// and a flag indicating whether the connection should be retried for authentication.
/// </remarks>
public Action<ProxyConnectParameters, Exception, bool> OnError;
}
/// <summary>
/// Base class for proxy implementations, providing common proxy configuration and behavior.
/// </summary>
/// <remarks>
/// The Proxy class serves as the base class for various proxy client implementations,
/// such as <see cref="HTTPProxy"/> and <see cref="SOCKSProxy"/>. It provides a foundation for configuring proxy settings and handling
/// proxy-related functionality common to all proxy types, like connecting to a proxy, setting up a request to go through the proxy
/// and deciding whether an address is usable with the proxy or the plugin must connect directly.
/// </remarks>
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 for authenticating with the proxy server.
/// </summary>
public Credentials Credentials { get; set; }
/// <summary>
/// List of exceptions for which the proxy should not be used. Elements of this list are compared to the Host (DNS or IP address) part of the uri.
/// </summary>
public List<string> Exceptions { get; set; }
/// <summary>
/// Initializes a new instance of the Proxy class with the specified proxy address and credentials.
/// </summary>
/// <param name="address">The address of the proxy server.</param>
/// <param name="credentials">The credentials for proxy authentication.</param>
internal Proxy(Uri address, Credentials credentials)
{
this.Address = address;
this.Credentials = credentials;
}
/// <summary>
/// Initiates a connection through the proxy server. Used during the negotiation steps.
/// </summary>
/// <param name="parameters">Parameters for the proxy connection.</param>
internal abstract void BeginConnect(ProxyConnectParameters parameters);
/// <summary>
/// Gets the request path to be used for proxy communication. In some cases with HTTPProxy, the request must send the whole uri as the request path.
/// </summary>
/// <param name="uri">The target URI.</param>
/// <returns>The request path for proxy communication.</returns>
public abstract string GetRequestPath(Uri uri);
/// <summary>
/// Sets up an HTTP request to use the proxy as needed.
/// </summary>
/// <param name="request">The HTTP request to set up.</param>
/// <returns><c>true</c> if the request should use the proxy; otherwise, <c>false</c>.</returns>
internal abstract bool SetupRequest(HTTPRequest request);
/// <summary>
/// Determines whether the proxy should be used for a specific address based on the configured exceptions.
/// </summary>
/// <param name="address">The address to check for proxy usage.</param>
/// <returns><c>true</c> if the proxy should be used for the address; otherwise, <c>false</c>.</returns>
public 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;
}
}
}