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 4.0 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. using Splat;
  2. using System;
  3. using System.Net;
  4. using System.Net.Sockets;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. namespace Shadowsocks.Net.Proxy
  10. {
  11. public class HttpProxy : IProxy, IEnableLogger
  12. {
  13. public EndPoint LocalEndPoint => _remote.LocalEndPoint;
  14. public EndPoint ProxyEndPoint { get; private set; }
  15. public EndPoint DestEndPoint { get; private set; }
  16. private readonly Socket _remote = new Socket(SocketType.Stream, ProtocolType.Tcp);
  17. private const string HTTP_CRLF = "\r\n";
  18. private const string HTTP_CONNECT_TEMPLATE =
  19. "CONNECT {0} HTTP/1.1" + HTTP_CRLF +
  20. "Host: {0}" + HTTP_CRLF +
  21. "Proxy-Connection: keep-alive" + HTTP_CRLF +
  22. "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36" + HTTP_CRLF +
  23. "{1}" + // Proxy-Authorization if any
  24. "" + HTTP_CRLF; // End with an empty line
  25. private const string PROXY_AUTH_TEMPLATE = "Proxy-Authorization: Basic {0}" + HTTP_CRLF;
  26. public void Shutdown(SocketShutdown how)
  27. {
  28. _remote.Shutdown(how);
  29. }
  30. public void Close()
  31. {
  32. _remote.Dispose();
  33. }
  34. private static readonly Regex HttpRespondHeaderRegex = new Regex(@"^(HTTP/1\.\d) (\d{3}) (.+)$", RegexOptions.Compiled);
  35. private int _respondLineCount = 0;
  36. private bool _established = false;
  37. private bool OnLineRead(string line, object state)
  38. {
  39. this.Log().Debug(line);
  40. if (_respondLineCount == 0)
  41. {
  42. var m = HttpRespondHeaderRegex.Match(line);
  43. if (m.Success)
  44. {
  45. var resultCode = m.Groups[2].Value;
  46. if ("200" != resultCode)
  47. {
  48. return true;
  49. }
  50. _established = true;
  51. }
  52. }
  53. else
  54. {
  55. if (string.IsNullOrEmpty(line))
  56. {
  57. return true;
  58. }
  59. }
  60. _respondLineCount++;
  61. return false;
  62. }
  63. private NetworkCredential auth;
  64. public async Task ConnectProxyAsync(EndPoint remoteEP, NetworkCredential auth = null, CancellationToken token = default)
  65. {
  66. ProxyEndPoint = remoteEP;
  67. this.auth = auth;
  68. await _remote.ConnectAsync(remoteEP);
  69. _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
  70. }
  71. public async Task ConnectRemoteAsync(EndPoint destEndPoint, CancellationToken token = default)
  72. {
  73. DestEndPoint = destEndPoint;
  74. String authInfo = "";
  75. if (auth != null)
  76. {
  77. string authKey = Convert.ToBase64String(Encoding.UTF8.GetBytes(auth.UserName + ":" + auth.Password));
  78. authInfo = string.Format(PROXY_AUTH_TEMPLATE, authKey);
  79. }
  80. string request = string.Format(HTTP_CONNECT_TEMPLATE, destEndPoint, authInfo);
  81. var b = Encoding.UTF8.GetBytes(request);
  82. await _remote.SendAsync(Encoding.UTF8.GetBytes(request), SocketFlags.None, token);
  83. // start line read
  84. LineReader reader = new LineReader(_remote, OnLineRead, (e, _) => throw e, (_1, _2, _3, _4) => { }, Encoding.UTF8, HTTP_CRLF, 1024, null);
  85. await reader.Finished;
  86. }
  87. public async Task<int> SendAsync(ReadOnlyMemory<byte> buffer, CancellationToken token = default)
  88. {
  89. return await _remote.SendAsync(buffer, SocketFlags.None, token);
  90. }
  91. public async Task<int> ReceiveAsync(Memory<byte> buffer, CancellationToken token = default)
  92. {
  93. return await _remote.ReceiveAsync(buffer, SocketFlags.None, token);
  94. }
  95. }
  96. }