You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
12 KiB
263 lines
12 KiB
#if !BESTHTTP_DISABLE_PROXY |
|
|
|
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; |
|
|
|
for (int i = 0; i < this.Exceptions.Count; ++i) |
|
if (address.Host.StartsWith(this.Exceptions[i])) |
|
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
|
|
|