mirror of
				https://github.com/bolucat/Archive.git
				synced 2025-10-31 11:57:05 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			211 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			211 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Net;
 | |
| using System.Net.Sockets;
 | |
| using System.Text;
 | |
| using System.Text.RegularExpressions;
 | |
| using System.Threading;
 | |
| using NLog;
 | |
| using Shadowsocks.Controller;
 | |
| using Shadowsocks.Util.Sockets;
 | |
| 
 | |
| namespace Shadowsocks.Proxy
 | |
| {
 | |
|     public class HttpProxy : IProxy
 | |
|     {
 | |
|         private static Logger logger = LogManager.GetCurrentClassLogger();
 | |
| 
 | |
|         private class FakeAsyncResult : IAsyncResult
 | |
|         {
 | |
|             public readonly HttpState innerState;
 | |
| 
 | |
|             private readonly IAsyncResult r;
 | |
| 
 | |
|             public FakeAsyncResult(IAsyncResult orig, HttpState state)
 | |
|             {
 | |
|                 r = orig;
 | |
|                 innerState = state;
 | |
|             }
 | |
| 
 | |
|             public bool IsCompleted => r.IsCompleted;
 | |
|             public WaitHandle AsyncWaitHandle => r.AsyncWaitHandle;
 | |
|             public object AsyncState => innerState.AsyncState;
 | |
|             public bool CompletedSynchronously => r.CompletedSynchronously;
 | |
|         }
 | |
| 
 | |
|         private class HttpState
 | |
|         {
 | |
| 
 | |
|             public AsyncCallback Callback { get; set; }
 | |
| 
 | |
|             public object AsyncState { get; set; }
 | |
| 
 | |
|             public Exception ex { get; set; }
 | |
|         }
 | |
| 
 | |
|         public EndPoint LocalEndPoint => _remote.LocalEndPoint;
 | |
|         public EndPoint ProxyEndPoint { get; private set; }
 | |
|         public EndPoint DestEndPoint { get; private set; }
 | |
| 
 | |
| 
 | |
|         private readonly WrappedSocket _remote = new WrappedSocket();
 | |
| 
 | |
| 
 | |
|         public void BeginConnectProxy(EndPoint remoteEP, AsyncCallback callback, object state)
 | |
|         {
 | |
|             ProxyEndPoint = remoteEP;
 | |
| 
 | |
|             _remote.BeginConnect(remoteEP, callback, state);
 | |
|         }
 | |
| 
 | |
|         public void EndConnectProxy(IAsyncResult asyncResult)
 | |
|         {
 | |
|             _remote.EndConnect(asyncResult);
 | |
|             _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
 | |
|         }
 | |
| 
 | |
|         private const string HTTP_CRLF = "\r\n";
 | |
|         private const string HTTP_CONNECT_TEMPLATE = 
 | |
|             "CONNECT {0} HTTP/1.1" + HTTP_CRLF + 
 | |
|             "Host: {0}" + HTTP_CRLF +
 | |
|             "Proxy-Connection: keep-alive" + HTTP_CRLF +
 | |
|             "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 +
 | |
|             "{1}" +         // Proxy-Authorization if any
 | |
|             "" + HTTP_CRLF; // End with an empty line
 | |
|         private const string PROXY_AUTH_TEMPLATE = "Proxy-Authorization: Basic {0}" + HTTP_CRLF;
 | |
| 
 | |
|         public void BeginConnectDest(EndPoint destEndPoint, AsyncCallback callback, object state, NetworkCredential auth = null)
 | |
|         {
 | |
|             DestEndPoint = destEndPoint;
 | |
|             String authInfo = "";
 | |
|             if (auth != null)
 | |
|             {
 | |
|                 string authKey = Convert.ToBase64String(Encoding.UTF8.GetBytes(auth.UserName + ":" + auth.Password));
 | |
|                 authInfo = string.Format(PROXY_AUTH_TEMPLATE, authKey);
 | |
|             }
 | |
|             string request = string.Format(HTTP_CONNECT_TEMPLATE, destEndPoint, authInfo);
 | |
| 
 | |
|             var b = Encoding.UTF8.GetBytes(request);
 | |
| 
 | |
|             var st = new HttpState();
 | |
|             st.Callback = callback;
 | |
|             st.AsyncState = state;
 | |
| 
 | |
|             _remote.BeginSend(b, 0, b.Length, 0, HttpRequestSendCallback, st);
 | |
|         }
 | |
| 
 | |
|         public void EndConnectDest(IAsyncResult asyncResult)
 | |
|         {
 | |
|             var state = ((FakeAsyncResult)asyncResult).innerState;
 | |
| 
 | |
|             if (state.ex != null)
 | |
|             {
 | |
|                 throw state.ex;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public void BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback,
 | |
|             object state)
 | |
|         {
 | |
|             _remote.BeginSend(buffer, offset, size, socketFlags, callback, state);
 | |
|         }
 | |
| 
 | |
|         public int EndSend(IAsyncResult asyncResult)
 | |
|         {
 | |
|             return _remote.EndSend(asyncResult);
 | |
|         }
 | |
| 
 | |
|         public void BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback,
 | |
|             object state)
 | |
|         {
 | |
|             _remote.BeginReceive(buffer, offset, size, socketFlags, callback, state);
 | |
|         }
 | |
| 
 | |
|         public int EndReceive(IAsyncResult asyncResult)
 | |
|         {
 | |
|             return _remote.EndReceive(asyncResult);
 | |
|         }
 | |
| 
 | |
|         public void Shutdown(SocketShutdown how)
 | |
|         {
 | |
|             _remote.Shutdown(how);
 | |
|         }
 | |
| 
 | |
|         public void Close()
 | |
|         {
 | |
|             _remote.Dispose();
 | |
|         }
 | |
| 
 | |
|         private void HttpRequestSendCallback(IAsyncResult ar)
 | |
|         {
 | |
|             var state = (HttpState) ar.AsyncState;
 | |
|             try
 | |
|             {
 | |
|                 _remote.EndSend(ar);
 | |
| 
 | |
|                 // start line read
 | |
|                 new LineReader(_remote, OnLineRead, OnException, OnFinish, Encoding.UTF8, HTTP_CRLF, 1024, new FakeAsyncResult(ar, state));
 | |
|             }
 | |
|             catch (Exception ex)
 | |
|             {
 | |
|                 state.ex = ex;
 | |
|                 state.Callback?.Invoke(new FakeAsyncResult(ar, state));
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private void OnFinish(byte[] lastBytes, int index, int length, object state)
 | |
|         {
 | |
|             var st = (FakeAsyncResult)state;
 | |
| 
 | |
|             if (st.innerState.ex == null)
 | |
|             {
 | |
|                 if (!_established)
 | |
|                 {
 | |
|                     st.innerState.ex = new Exception(I18N.GetString("Proxy request failed"));
 | |
|                 }
 | |
|                 // TODO: save last bytes
 | |
|             }
 | |
|             st.innerState.Callback?.Invoke(st);
 | |
|         }
 | |
| 
 | |
|         private void OnException(Exception ex, object state)
 | |
|         {
 | |
|             var st = (FakeAsyncResult) state;
 | |
| 
 | |
|             st.innerState.ex = ex;
 | |
|         }
 | |
| 
 | |
|         private static readonly Regex HttpRespondHeaderRegex = new Regex(@"^(HTTP/1\.\d) (\d{3}) (.+)$", RegexOptions.Compiled);
 | |
|         private int _respondLineCount = 0;
 | |
|         private bool _established = false;
 | |
| 
 | |
|         private bool OnLineRead(string line, object state)
 | |
|         {
 | |
|             logger.Trace(line);
 | |
| 
 | |
|             if (_respondLineCount == 0)
 | |
|             {
 | |
|                 var m = HttpRespondHeaderRegex.Match(line);
 | |
|                 if (m.Success)
 | |
|                 {
 | |
|                     var resultCode = m.Groups[2].Value;
 | |
|                     if ("200" != resultCode)
 | |
|                     {
 | |
|                         return true;
 | |
|                     }
 | |
|                     _established = true;
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 if (string.IsNullOrEmpty(line))
 | |
|                 {
 | |
|                     return true;
 | |
|                 }
 | |
|             }
 | |
|             _respondLineCount++;
 | |
| 
 | |
|             return false;
 | |
|         }
 | |
|     }
 | |
| }
 | 
![github-action[bot]](/assets/img/avatar_default.png)