HTTPProxy.cs 8.9 KB

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