You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

HttpProxy.cs 6.7 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. using System;
  2. using System.Net;
  3. using System.Net.Sockets;
  4. using System.Text;
  5. using System.Text.RegularExpressions;
  6. using System.Threading;
  7. using NLog;
  8. using Shadowsocks.Controller;
  9. using Shadowsocks.Util.Sockets;
  10. namespace Shadowsocks.Proxy
  11. {
  12. public class HttpProxy : IProxy
  13. {
  14. private static Logger logger = LogManager.GetCurrentClassLogger();
  15. private class FakeAsyncResult : IAsyncResult
  16. {
  17. public readonly HttpState innerState;
  18. private readonly IAsyncResult r;
  19. public FakeAsyncResult(IAsyncResult orig, HttpState state)
  20. {
  21. r = orig;
  22. innerState = state;
  23. }
  24. public bool IsCompleted => r.IsCompleted;
  25. public WaitHandle AsyncWaitHandle => r.AsyncWaitHandle;
  26. public object AsyncState => innerState.AsyncState;
  27. public bool CompletedSynchronously => r.CompletedSynchronously;
  28. }
  29. private class HttpState
  30. {
  31. public AsyncCallback Callback { get; set; }
  32. public object AsyncState { get; set; }
  33. public int BytesToRead;
  34. public Exception ex { get; set; }
  35. }
  36. public EndPoint LocalEndPoint => _remote.LocalEndPoint;
  37. public EndPoint ProxyEndPoint { get; private set; }
  38. public EndPoint DestEndPoint { get; private set; }
  39. private readonly WrappedSocket _remote = new WrappedSocket();
  40. public void BeginConnectProxy(EndPoint remoteEP, AsyncCallback callback, object state)
  41. {
  42. ProxyEndPoint = remoteEP;
  43. _remote.BeginConnect(remoteEP, callback, state);
  44. }
  45. public void EndConnectProxy(IAsyncResult asyncResult)
  46. {
  47. _remote.EndConnect(asyncResult);
  48. _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
  49. }
  50. private const string HTTP_CRLF = "\r\n";
  51. private const string HTTP_CONNECT_TEMPLATE =
  52. "CONNECT {0} HTTP/1.1" + HTTP_CRLF +
  53. "Host: {0}" + HTTP_CRLF +
  54. "Proxy-Connection: keep-alive" + HTTP_CRLF +
  55. "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" + HTTP_CRLF +
  56. "{1}" + // Proxy-Authorization if any
  57. "" + HTTP_CRLF; // End with an empty line
  58. private const string PROXY_AUTH_TEMPLATE = "Proxy-Authorization: Basic {0}" + HTTP_CRLF;
  59. public void BeginConnectDest(EndPoint destEndPoint, AsyncCallback callback, object state, NetworkCredential auth = null)
  60. {
  61. DestEndPoint = destEndPoint;
  62. String authInfo = "";
  63. if (auth != null)
  64. {
  65. string authKey = Convert.ToBase64String(Encoding.UTF8.GetBytes(auth.UserName + ":" + auth.Password));
  66. authInfo = string.Format(PROXY_AUTH_TEMPLATE, authKey);
  67. }
  68. string request = string.Format(HTTP_CONNECT_TEMPLATE, destEndPoint, authInfo);
  69. var b = Encoding.UTF8.GetBytes(request);
  70. var st = new HttpState();
  71. st.Callback = callback;
  72. st.AsyncState = state;
  73. _remote.BeginSend(b, 0, b.Length, 0, HttpRequestSendCallback, st);
  74. }
  75. public void EndConnectDest(IAsyncResult asyncResult)
  76. {
  77. var state = ((FakeAsyncResult)asyncResult).innerState;
  78. if (state.ex != null)
  79. {
  80. throw state.ex;
  81. }
  82. }
  83. public void BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback,
  84. object state)
  85. {
  86. _remote.BeginSend(buffer, offset, size, socketFlags, callback, state);
  87. }
  88. public int EndSend(IAsyncResult asyncResult)
  89. {
  90. return _remote.EndSend(asyncResult);
  91. }
  92. public void BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback,
  93. object state)
  94. {
  95. _remote.BeginReceive(buffer, offset, size, socketFlags, callback, state);
  96. }
  97. public int EndReceive(IAsyncResult asyncResult)
  98. {
  99. return _remote.EndReceive(asyncResult);
  100. }
  101. public void Shutdown(SocketShutdown how)
  102. {
  103. _remote.Shutdown(how);
  104. }
  105. public void Close()
  106. {
  107. _remote.Dispose();
  108. }
  109. private void HttpRequestSendCallback(IAsyncResult ar)
  110. {
  111. var state = (HttpState) ar.AsyncState;
  112. try
  113. {
  114. _remote.EndSend(ar);
  115. // start line read
  116. new LineReader(_remote, OnLineRead, OnException, OnFinish, Encoding.UTF8, HTTP_CRLF, 1024, new FakeAsyncResult(ar, state));
  117. }
  118. catch (Exception ex)
  119. {
  120. state.ex = ex;
  121. state.Callback?.Invoke(new FakeAsyncResult(ar, state));
  122. }
  123. }
  124. private void OnFinish(byte[] lastBytes, int index, int length, object state)
  125. {
  126. var st = (FakeAsyncResult)state;
  127. if (st.innerState.ex == null)
  128. {
  129. if (!_established)
  130. {
  131. st.innerState.ex = new Exception(I18N.GetString("Proxy request failed"));
  132. }
  133. // TODO: save last bytes
  134. }
  135. st.innerState.Callback?.Invoke(st);
  136. }
  137. private void OnException(Exception ex, object state)
  138. {
  139. var st = (FakeAsyncResult) state;
  140. st.innerState.ex = ex;
  141. }
  142. private static readonly Regex HttpRespondHeaderRegex = new Regex(@"^(HTTP/1\.\d) (\d{3}) (.+)$", RegexOptions.Compiled);
  143. private int _respondLineCount = 0;
  144. private bool _established = false;
  145. private bool OnLineRead(string line, object state)
  146. {
  147. logger.Trace(line);
  148. if (_respondLineCount == 0)
  149. {
  150. var m = HttpRespondHeaderRegex.Match(line);
  151. if (m.Success)
  152. {
  153. var resultCode = m.Groups[2].Value;
  154. if ("200" != resultCode)
  155. {
  156. return true;
  157. }
  158. _established = true;
  159. }
  160. }
  161. else
  162. {
  163. if (line.IsNullOrEmpty())
  164. {
  165. return true;
  166. }
  167. }
  168. _respondLineCount++;
  169. return false;
  170. }
  171. }
  172. }