HTTPProxy.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. #if !BESTHTTP_DISABLE_PROXY && (!UNITY_WEBGL || UNITY_EDITOR)
  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. string host = address.Host;
  39. // https://github.com/httplib2/httplib2/issues/94
  40. // If domain starts with a dot (example: .example.com):
  41. // 1. Use endswith to match any subdomain (foo.example.com should match)
  42. // 2. Remove the dot and do an exact match (example.com should also match)
  43. //
  44. // If domain does not start with a dot (example: example.com):
  45. // 1. It should be an exact match.
  46. for (int i = 0; i < this.Exceptions.Count; ++i)
  47. {
  48. var exception = this.Exceptions[i];
  49. if (exception == "*")
  50. return false;
  51. if (exception.StartsWith("."))
  52. {
  53. // Use EndsWith to match any subdomain
  54. if (host.EndsWith(exception))
  55. return false;
  56. // Remove the dot and
  57. exception = exception.Substring(1);
  58. }
  59. // do an exact match
  60. if (host.Equals(exception))
  61. return false;
  62. }
  63. return true;
  64. }
  65. }
  66. public sealed class HTTPProxy : Proxy
  67. {
  68. /// <summary>
  69. /// True if the proxy can act as a transparent proxy
  70. /// </summary>
  71. public bool IsTransparent { get; set; }
  72. /// <summary>
  73. /// Some non-transparent proxies are except only the path and query of the request uri. Default value is true
  74. /// </summary>
  75. public bool SendWholeUri { get; set; }
  76. /// <summary>
  77. /// 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)
  78. /// </summary>
  79. public bool NonTransparentForHTTPS { get; set; }
  80. public HTTPProxy(Uri address)
  81. :this(address, null, false)
  82. {}
  83. public HTTPProxy(Uri address, Credentials credentials)
  84. :this(address, credentials, false)
  85. {}
  86. public HTTPProxy(Uri address, Credentials credentials, bool isTransparent)
  87. :this(address, credentials, isTransparent, true)
  88. { }
  89. public HTTPProxy(Uri address, Credentials credentials, bool isTransparent, bool sendWholeUri)
  90. : this(address, credentials, isTransparent, sendWholeUri, true)
  91. { }
  92. public HTTPProxy(Uri address, Credentials credentials, bool isTransparent, bool sendWholeUri, bool nonTransparentForHTTPS)
  93. :base(address, credentials)
  94. {
  95. this.IsTransparent = isTransparent;
  96. this.SendWholeUri = sendWholeUri;
  97. this.NonTransparentForHTTPS = nonTransparentForHTTPS;
  98. }
  99. internal override string GetRequestPath(Uri uri)
  100. {
  101. return this.SendWholeUri ? uri.OriginalString : uri.GetRequestPathAndQueryURL();
  102. }
  103. internal override bool SetupRequest(HTTPRequest request)
  104. {
  105. if (request == null || request.Response == null || !this.IsTransparent)
  106. return false;
  107. string authHeader = DigestStore.FindBest(request.Response.GetHeaderValues("proxy-authenticate"));
  108. if (!string.IsNullOrEmpty(authHeader))
  109. {
  110. var digest = DigestStore.GetOrCreate(request.Proxy.Address);
  111. digest.ParseChallange(authHeader);
  112. if (request.Proxy.Credentials != null && digest.IsUriProtected(request.Proxy.Address) && (!request.HasHeader("Proxy-Authorization") || digest.Stale))
  113. {
  114. switch (request.Proxy.Credentials.Type)
  115. {
  116. case AuthenticationTypes.Basic:
  117. // With Basic authentication we don't want to wait for a challenge, we will send the hash with the first request
  118. request.SetHeader("Proxy-Authorization", string.Concat("Basic ", Convert.ToBase64String(Encoding.UTF8.GetBytes(request.Proxy.Credentials.UserName + ":" + request.Proxy.Credentials.Password))));
  119. return true;
  120. case AuthenticationTypes.Unknown:
  121. case AuthenticationTypes.Digest:
  122. //var digest = DigestStore.Get(request.Proxy.Address);
  123. if (digest != null)
  124. {
  125. string authentication = digest.GenerateResponseHeader(request, request.Proxy.Credentials, true);
  126. if (!string.IsNullOrEmpty(authentication))
  127. {
  128. request.SetHeader("Proxy-Authorization", authentication);
  129. return true;
  130. }
  131. }
  132. break;
  133. }
  134. }
  135. }
  136. return false;
  137. }
  138. internal override void Connect(Stream stream, HTTPRequest request)
  139. {
  140. bool isSecure = HTTPProtocolFactory.IsSecureProtocol(request.CurrentUri);
  141. if (!this.IsTransparent || (isSecure && this.NonTransparentForHTTPS))
  142. {
  143. using (var bufferedStream = new WriteOnlyBufferedStream(stream, HTTPRequest.UploadChunkSize))
  144. using (var outStream = new BinaryWriter(bufferedStream, Encoding.UTF8))
  145. {
  146. bool retry;
  147. do
  148. {
  149. // If we have to because of a authentication request, we will switch it to true
  150. retry = false;
  151. string connectStr = string.Format("CONNECT {0}:{1} HTTP/1.1", request.CurrentUri.Host, request.CurrentUri.Port.ToString());
  152. HTTPManager.Logger.Information("HTTPProxy", "Sending " + connectStr, request.Context);
  153. outStream.SendAsASCII(connectStr);
  154. outStream.Write(HTTPRequest.EOL);
  155. outStream.SendAsASCII("Proxy-Connection: Keep-Alive");
  156. outStream.Write(HTTPRequest.EOL);
  157. outStream.SendAsASCII("Connection: Keep-Alive");
  158. outStream.Write(HTTPRequest.EOL);
  159. outStream.SendAsASCII(string.Format("Host: {0}:{1}", request.CurrentUri.Host, request.CurrentUri.Port.ToString()));
  160. outStream.Write(HTTPRequest.EOL);
  161. // Proxy Authentication
  162. if (this.Credentials != null)
  163. {
  164. switch (this.Credentials.Type)
  165. {
  166. case AuthenticationTypes.Basic:
  167. {
  168. // With Basic authentication we don't want to wait for a challenge, we will send the hash with the first request
  169. var buff = string.Format("Proxy-Authorization: {0}", string.Concat("Basic ", Convert.ToBase64String(Encoding.UTF8.GetBytes(this.Credentials.UserName + ":" + this.Credentials.Password)))).GetASCIIBytes();
  170. outStream.Write(buff.Data, buff.Offset, buff.Count);
  171. BufferPool.Release(buff);
  172. outStream.Write(HTTPRequest.EOL);
  173. break;
  174. }
  175. case AuthenticationTypes.Unknown:
  176. case AuthenticationTypes.Digest:
  177. {
  178. var digest = DigestStore.Get(this.Address);
  179. if (digest != null)
  180. {
  181. string authentication = digest.GenerateResponseHeader(request, this.Credentials, true);
  182. if (!string.IsNullOrEmpty(authentication))
  183. {
  184. string auth = string.Format("Proxy-Authorization: {0}", authentication);
  185. if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
  186. HTTPManager.Logger.Information("HTTPProxy", "Sending proxy authorization header: " + auth, request.Context);
  187. var buff = auth.GetASCIIBytes();
  188. outStream.Write(buff.Data, buff.Offset, buff.Count);
  189. BufferPool.Release(buff);
  190. outStream.Write(HTTPRequest.EOL);
  191. }
  192. }
  193. break;
  194. }
  195. }
  196. }
  197. outStream.Write(HTTPRequest.EOL);
  198. // Make sure to send all the wrote data to the wire
  199. outStream.Flush();
  200. request.ProxyResponse = new HTTPResponse(request, stream, false, false, true);
  201. // Read back the response of the proxy
  202. if (!request.ProxyResponse.Receive(-1, true))
  203. throw new Exception("Connection to the Proxy Server failed!");
  204. if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
  205. HTTPManager.Logger.Information("HTTPProxy", "Proxy returned - status code: " + request.ProxyResponse.StatusCode + " message: " + request.ProxyResponse.Message + " Body: " + request.ProxyResponse.DataAsText, request.Context);
  206. switch (request.ProxyResponse.StatusCode)
  207. {
  208. // Proxy authentication required
  209. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.8
  210. case 407:
  211. {
  212. string authHeader = DigestStore.FindBest(request.ProxyResponse.GetHeaderValues("proxy-authenticate"));
  213. if (!string.IsNullOrEmpty(authHeader))
  214. {
  215. var digest = DigestStore.GetOrCreate(this.Address);
  216. digest.ParseChallange(authHeader);
  217. if (this.Credentials != null && digest.IsUriProtected(this.Address) && (!request.HasHeader("Proxy-Authorization") || digest.Stale))
  218. retry = true;
  219. }
  220. if (!retry)
  221. 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));
  222. break;
  223. }
  224. default:
  225. if (!request.ProxyResponse.IsSuccess)
  226. throw new Exception(string.Format("Proxy returned Status Code: \"{0}\", Message: \"{1}\" and Response: {2}", request.ProxyResponse.StatusCode, request.ProxyResponse.Message, request.ProxyResponse.DataAsText));
  227. break;
  228. }
  229. } while (retry);
  230. } // using outstream
  231. }
  232. }
  233. }
  234. }
  235. #endif