HTTPProxy.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. #if !BESTHTTP_DISABLE_PROXY
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Text;
  6. using BestHTTP.Authentication;
  7. using BestHTTP.Connections;
  8. using BestHTTP.Extensions;
  9. using BestHTTP.PlatformSupport.Memory;
  10. namespace BestHTTP
  11. {
  12. public abstract class Proxy
  13. {
  14. /// <summary>
  15. /// Address of the proxy server. It has to be in the http://proxyaddress:port form.
  16. /// </summary>
  17. public Uri Address { get; set; }
  18. /// <summary>
  19. /// Credentials of the proxy
  20. /// </summary>
  21. public Credentials Credentials { get; set; }
  22. /// <summary>
  23. /// 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.
  24. /// </summary>
  25. public List<string> Exceptions { get; set; }
  26. internal Proxy(Uri address, Credentials credentials)
  27. {
  28. this.Address = address;
  29. this.Credentials = credentials;
  30. }
  31. internal abstract void Connect(Stream stream, HTTPRequest request);
  32. internal abstract string GetRequestPath(Uri uri);
  33. internal abstract bool SetupRequest(HTTPRequest request);
  34. internal bool UseProxyForAddress(Uri address)
  35. {
  36. if (this.Exceptions == null)
  37. return true;
  38. for (int i = 0; i < this.Exceptions.Count; ++i)
  39. if (address.Host.StartsWith(this.Exceptions[i]))
  40. return false;
  41. return true;
  42. }
  43. }
  44. public sealed class HTTPProxy : Proxy
  45. {
  46. /// <summary>
  47. /// True if the proxy can act as a transparent proxy
  48. /// </summary>
  49. public bool IsTransparent { get; set; }
  50. /// <summary>
  51. /// Some non-transparent proxies are except only the path and query of the request uri. Default value is true
  52. /// </summary>
  53. public bool SendWholeUri { get; set; }
  54. /// <summary>
  55. /// 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)
  56. /// </summary>
  57. public bool NonTransparentForHTTPS { get; set; }
  58. public HTTPProxy(Uri address)
  59. :this(address, null, false)
  60. {}
  61. public HTTPProxy(Uri address, Credentials credentials)
  62. :this(address, credentials, false)
  63. {}
  64. public HTTPProxy(Uri address, Credentials credentials, bool isTransparent)
  65. :this(address, credentials, isTransparent, true)
  66. { }
  67. public HTTPProxy(Uri address, Credentials credentials, bool isTransparent, bool sendWholeUri)
  68. : this(address, credentials, isTransparent, sendWholeUri, true)
  69. { }
  70. public HTTPProxy(Uri address, Credentials credentials, bool isTransparent, bool sendWholeUri, bool nonTransparentForHTTPS)
  71. :base(address, credentials)
  72. {
  73. this.IsTransparent = isTransparent;
  74. this.SendWholeUri = sendWholeUri;
  75. this.NonTransparentForHTTPS = nonTransparentForHTTPS;
  76. }
  77. internal override string GetRequestPath(Uri uri)
  78. {
  79. return this.SendWholeUri ? uri.OriginalString : uri.GetRequestPathAndQueryURL();
  80. }
  81. internal override bool SetupRequest(HTTPRequest request)
  82. {
  83. if (request == null || request.Response == null || !this.IsTransparent)
  84. return false;
  85. string authHeader = DigestStore.FindBest(request.Response.GetHeaderValues("proxy-authenticate"));
  86. if (!string.IsNullOrEmpty(authHeader))
  87. {
  88. var digest = DigestStore.GetOrCreate(request.Proxy.Address);
  89. digest.ParseChallange(authHeader);
  90. if (request.Proxy.Credentials != null && digest.IsUriProtected(request.Proxy.Address) && (!request.HasHeader("Proxy-Authorization") || digest.Stale))
  91. {
  92. switch (request.Proxy.Credentials.Type)
  93. {
  94. case AuthenticationTypes.Basic:
  95. // With Basic authentication we don't want to wait for a challenge, we will send the hash with the first request
  96. request.SetHeader("Proxy-Authorization", string.Concat("Basic ", Convert.ToBase64String(Encoding.UTF8.GetBytes(request.Proxy.Credentials.UserName + ":" + request.Proxy.Credentials.Password))));
  97. return true;
  98. case AuthenticationTypes.Unknown:
  99. case AuthenticationTypes.Digest:
  100. //var digest = DigestStore.Get(request.Proxy.Address);
  101. if (digest != null)
  102. {
  103. string authentication = digest.GenerateResponseHeader(request, request.Proxy.Credentials, true);
  104. if (!string.IsNullOrEmpty(authentication))
  105. {
  106. request.SetHeader("Proxy-Authorization", authentication);
  107. return true;
  108. }
  109. }
  110. break;
  111. }
  112. }
  113. }
  114. return false;
  115. }
  116. internal override void Connect(Stream stream, HTTPRequest request)
  117. {
  118. bool isSecure = HTTPProtocolFactory.IsSecureProtocol(request.CurrentUri);
  119. if (!this.IsTransparent || (isSecure && this.NonTransparentForHTTPS))
  120. {
  121. using (var bufferedStream = new WriteOnlyBufferedStream(stream, HTTPRequest.UploadChunkSize))
  122. using (var outStream = new BinaryWriter(bufferedStream, Encoding.UTF8))
  123. {
  124. bool retry;
  125. do
  126. {
  127. // If we have to because of a authentication request, we will switch it to true
  128. retry = false;
  129. string connectStr = string.Format("CONNECT {0}:{1} HTTP/1.1", request.CurrentUri.Host, request.CurrentUri.Port.ToString());
  130. HTTPManager.Logger.Information("HTTPProxy", "Sending " + connectStr, request.Context);
  131. outStream.SendAsASCII(connectStr);
  132. outStream.Write(HTTPRequest.EOL);
  133. outStream.SendAsASCII("Proxy-Connection: Keep-Alive");
  134. outStream.Write(HTTPRequest.EOL);
  135. outStream.SendAsASCII("Connection: Keep-Alive");
  136. outStream.Write(HTTPRequest.EOL);
  137. outStream.SendAsASCII(string.Format("Host: {0}:{1}", request.CurrentUri.Host, request.CurrentUri.Port.ToString()));
  138. outStream.Write(HTTPRequest.EOL);
  139. // Proxy Authentication
  140. if (this.Credentials != null)
  141. {
  142. switch (this.Credentials.Type)
  143. {
  144. case AuthenticationTypes.Basic:
  145. // With Basic authentication we don't want to wait for a challenge, we will send the hash with the first request
  146. outStream.Write(string.Format("Proxy-Authorization: {0}", string.Concat("Basic ", Convert.ToBase64String(Encoding.UTF8.GetBytes(this.Credentials.UserName + ":" + this.Credentials.Password)))).GetASCIIBytes());
  147. outStream.Write(HTTPRequest.EOL);
  148. break;
  149. case AuthenticationTypes.Unknown:
  150. case AuthenticationTypes.Digest:
  151. var digest = DigestStore.Get(this.Address);
  152. if (digest != null)
  153. {
  154. string authentication = digest.GenerateResponseHeader(request, this.Credentials, true);
  155. if (!string.IsNullOrEmpty(authentication))
  156. {
  157. string auth = string.Format("Proxy-Authorization: {0}", authentication);
  158. if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
  159. HTTPManager.Logger.Information("HTTPProxy", "Sending proxy authorization header: " + auth, request.Context);
  160. var bytes = auth.GetASCIIBytes();
  161. outStream.Write(bytes);
  162. outStream.Write(HTTPRequest.EOL);
  163. BufferPool.Release(bytes);
  164. }
  165. }
  166. break;
  167. }
  168. }
  169. outStream.Write(HTTPRequest.EOL);
  170. // Make sure to send all the wrote data to the wire
  171. outStream.Flush();
  172. request.ProxyResponse = new HTTPResponse(request, stream, false, false, true);
  173. // Read back the response of the proxy
  174. if (!request.ProxyResponse.Receive(-1, true))
  175. throw new Exception("Connection to the Proxy Server failed!");
  176. if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
  177. HTTPManager.Logger.Information("HTTPProxy", "Proxy returned - status code: " + request.ProxyResponse.StatusCode + " message: " + request.ProxyResponse.Message + " Body: " + request.ProxyResponse.DataAsText, request.Context);
  178. switch (request.ProxyResponse.StatusCode)
  179. {
  180. // Proxy authentication required
  181. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.8
  182. case 407:
  183. {
  184. string authHeader = DigestStore.FindBest(request.ProxyResponse.GetHeaderValues("proxy-authenticate"));
  185. if (!string.IsNullOrEmpty(authHeader))
  186. {
  187. var digest = DigestStore.GetOrCreate(this.Address);
  188. digest.ParseChallange(authHeader);
  189. if (this.Credentials != null && digest.IsUriProtected(this.Address) && (!request.HasHeader("Proxy-Authorization") || digest.Stale))
  190. retry = true;
  191. }
  192. if (!retry)
  193. 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));
  194. break;
  195. }
  196. default:
  197. if (!request.ProxyResponse.IsSuccess)
  198. throw new Exception(string.Format("Proxy returned Status Code: \"{0}\", Message: \"{1}\" and Response: {2}", request.ProxyResponse.StatusCode, request.ProxyResponse.Message, request.ProxyResponse.DataAsText));
  199. break;
  200. }
  201. } while (retry);
  202. } // using outstream
  203. }
  204. }
  205. }
  206. }
  207. #endif