PageRenderTime 24ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/HttpToSocks.cs

https://bitbucket.org/RoliSoft/rs-tv-show-tracker
C# | 427 lines | 245 code | 64 blank | 118 comment | 28 complexity | 3bfc79a4ffabc94fcbe8d573f33c0736 MD5 | raw file
Possible License(s): Apache-2.0, LGPL-3.0, LGPL-2.0, CC-BY-SA-3.0, JSON, MIT
  1. namespace RoliSoft.TVShowTracker
  2. {
  3. using System;
  4. using System.IO;
  5. using System.Net;
  6. using System.Net.Sockets;
  7. using System.Text;
  8. using System.Text.RegularExpressions;
  9. using System.Threading;
  10. using Starksoft.Net.Proxy;
  11. /// <summary>
  12. /// Provides an HTTP proxy and forwards the incoming requests to an external SOCKSv5 proxy.
  13. /// </summary>
  14. public class HttpToSocks
  15. {
  16. /// <summary>
  17. /// A regular expression to find the URL in the HTTP request.
  18. /// </summary>
  19. public static Regex URLRegex = new Regex(@"https?://(?<host>[^/:]+)(?:\:(?<port>[0-9]+))?(?<path>/[^\s$]*)");
  20. /// <summary>
  21. /// Gets the local HTTP proxy.
  22. /// </summary>
  23. public Proxy LocalProxy
  24. {
  25. get
  26. {
  27. if (_server == null)
  28. {
  29. return null;
  30. }
  31. return new Proxy
  32. {
  33. Type = ProxyType.Http,
  34. Host = "[::1]",
  35. Port = ((IPEndPoint)_server.LocalEndpoint).Port
  36. };
  37. }
  38. }
  39. /// <summary>
  40. /// Gets or sets the remote SOCKS proxy.
  41. /// </summary>
  42. /// <value>
  43. /// The remote SOCKS proxy.
  44. /// </value>
  45. public Proxy RemoteProxy { get; set; }
  46. private TcpListener _server;
  47. private Thread _timeout;
  48. /// <summary>
  49. /// Starts listening for incoming connections at ::1 on a random port.
  50. /// </summary>
  51. public void Listen()
  52. {
  53. _server = new TcpListener(IPAddress.IPv6Loopback, 0);
  54. _server.Start();
  55. _server.BeginAcceptSocket(AcceptClient, null);
  56. _timeout = new Thread(Timeout);
  57. _timeout.Start();
  58. }
  59. /// <summary>
  60. /// Kills the server after a minute.
  61. /// </summary>
  62. private void Timeout()
  63. {
  64. Thread.Sleep(TimeSpan.FromMinutes(1));
  65. if (_server != null)
  66. {
  67. try { _server.Stop(); } catch { }
  68. _server = null;
  69. }
  70. }
  71. /// <summary>
  72. /// Accepts the incoming request, processes it, and shuts down the whole server.
  73. /// </summary>
  74. /// <param name="asyncResult">The async result.</param>
  75. private void AcceptClient(IAsyncResult asyncResult)
  76. {
  77. try
  78. {
  79. _server.BeginAcceptSocket(AcceptClient, null);
  80. }
  81. catch
  82. {
  83. return;
  84. }
  85. using (var client = _server.EndAcceptTcpClient(asyncResult))
  86. using (var stream = client.GetStream())
  87. {
  88. client.Client.NoDelay = true;
  89. stream.ReadTimeout = 100;
  90. ProcessHTTPRequest(stream);
  91. }
  92. }
  93. /// <summary>
  94. /// Reads and processes the HTTP request from the stream.
  95. /// </summary>
  96. /// <param name="requestStream">The request stream.</param>
  97. private void ProcessHTTPRequest(NetworkStream requestStream)
  98. {
  99. string host, path, postData;
  100. string[] request;
  101. var headers = new StringBuilder();
  102. var port = 80;
  103. using (var ms = new MemoryStream())
  104. {
  105. CopyStreamToStream(requestStream, ms);
  106. ms.Position = 0;
  107. using (var sr = new StreamReader(ms, Encoding.UTF8, false))
  108. {
  109. // [0]GET [1]/index.php [2]HTTP/1.1
  110. request = (sr.ReadLine() ?? string.Empty).Split(' ');
  111. if (request[0] == "CONNECT")
  112. {
  113. sr.Close();
  114. var dest = request[1].Split(':');
  115. TunnelRequest(requestStream, dest[0], dest[1].ToInteger());
  116. return;
  117. }
  118. var m = URLRegex.Match(request[1]);
  119. host = m.Groups["host"].Value;
  120. path = m.Groups["path"].Value;
  121. if (m.Groups["port"].Success)
  122. {
  123. port = m.Groups["port"].Value.ToInteger();
  124. }
  125. // read headers
  126. while (true)
  127. {
  128. var header = (sr.ReadLine() ?? string.Empty).Trim();
  129. if (string.IsNullOrWhiteSpace(header))
  130. {
  131. break;
  132. }
  133. if (header.StartsWith("Connection:") || header.StartsWith("Proxy-Connection:"))
  134. {
  135. continue;
  136. }
  137. headers.AppendLine(header);
  138. }
  139. headers.AppendLine("Connection: close");
  140. // read post data
  141. if (request[0] == "POST")
  142. {
  143. postData = sr.ReadToEnd();
  144. }
  145. else
  146. {
  147. postData = null;
  148. }
  149. }
  150. }
  151. var finalRequest = new StringBuilder();
  152. finalRequest.AppendLine(request[0] + " " + path + " " + request[2]);
  153. finalRequest.Append(headers);
  154. finalRequest.AppendLine();
  155. if (postData != null)
  156. {
  157. finalRequest.Append(postData);
  158. }
  159. ForwardRequest(Encoding.UTF8.GetBytes(finalRequest.ToString()), requestStream, host, port);
  160. }
  161. /// <summary>
  162. /// Opens a connection through the proxy and forwards the received request.
  163. /// </summary>
  164. /// <param name="requestData">The HTTP proxy's request data.</param>
  165. /// <param name="responseStream">The stream to write the SOCKS proxy's response to.</param>
  166. /// <param name="destHost">The destination host.</param>
  167. /// <param name="destPort">The destination port.</param>
  168. private void ForwardRequest(byte[] requestData, NetworkStream responseStream, string destHost, int destPort)
  169. {
  170. using (var client = RemoteProxy.CreateProxy().CreateConnection(destHost, destPort))
  171. using (var stream = client.GetStream())
  172. {
  173. client.Client.NoDelay = true;
  174. stream.ReadTimeout = 100;
  175. CopyBytesToStream(requestData, stream);
  176. CopyStreamToStream(stream, responseStream);
  177. }
  178. }
  179. /// <summary>
  180. /// Opens a connection through the proxy and creates a tunnel between the two streams.
  181. /// </summary>
  182. /// <param name="responseStream">The stream to write the SOCKS proxy's response to.</param>
  183. /// <param name="destHost">The destination host.</param>
  184. /// <param name="destPort">The destination port.</param>
  185. private void TunnelRequest(NetworkStream responseStream, string destHost, int destPort)
  186. {
  187. CopyStringToStream("HTTP/1.1 200 Tunnel established\r\nProxy-Connection: Keep-Alive\r\n\r\n", responseStream);
  188. using (var client = RemoteProxy.CreateProxy().CreateConnection(destHost, destPort))
  189. using (var stream = client.GetStream())
  190. {
  191. client.Client.NoDelay = true;
  192. stream.ReadTimeout = 100;
  193. while (true)
  194. {
  195. try
  196. {
  197. if (responseStream.DataAvailable)
  198. {
  199. CopyStreamToStream(responseStream, stream);
  200. }
  201. if (stream.DataAvailable)
  202. {
  203. CopyStreamToStream(stream, responseStream);
  204. }
  205. }
  206. catch
  207. {
  208. break;
  209. }
  210. }
  211. }
  212. }
  213. /// <summary>
  214. /// Copies the first stream's content from the current position to the second stream.
  215. /// </summary>
  216. /// <param name="source">The source stream.</param>
  217. /// <param name="destionation">The destionation stream.</param>
  218. /// <param name="flush">if set to <c>true</c>, <c>Flush()</c> will be called on the destination stream after finish.</param>
  219. /// <param name="bufferLength">Length of the buffer.</param>
  220. public static void CopyStreamToStream(Stream source, Stream destionation, bool flush = true, int bufferLength = 4096)
  221. {
  222. var buffer = new byte[bufferLength];
  223. while (true)
  224. {
  225. int i;
  226. try
  227. {
  228. i = source.Read(buffer, 0, bufferLength);
  229. }
  230. catch
  231. {
  232. break;
  233. }
  234. if (i == 0)
  235. {
  236. break;
  237. }
  238. destionation.Write(buffer, 0, i);
  239. }
  240. if (flush)
  241. {
  242. destionation.Flush();
  243. }
  244. }
  245. /// <summary>
  246. /// Copies the specified byte array to the destination stream.
  247. /// </summary>
  248. /// <param name="source">The source byte array.</param>
  249. /// <param name="destionation">The destionation stream.</param>
  250. /// <param name="flush">if set to <c>true</c>, <c>Flush()</c> will be called on the destination stream after finish.</param>
  251. public static void CopyBytesToStream(byte[] source, Stream destionation, bool flush = true)
  252. {
  253. destionation.Write(source, 0, source.Length);
  254. if (flush)
  255. {
  256. destionation.Flush();
  257. }
  258. }
  259. /// <summary>
  260. /// Copies the specified string to the destination stream.
  261. /// </summary>
  262. /// <param name="source">The source string.</param>
  263. /// <param name="destionation">The destionation stream.</param>
  264. /// <param name="flush">if set to <c>true</c>, <c>Flush()</c> will be called on the destination stream after finish.</param>
  265. /// <param name="encoding">The encoding to use for conversion.</param>
  266. public static void CopyStringToStream(string source, Stream destionation, bool flush = true, Encoding encoding = null)
  267. {
  268. CopyBytesToStream((encoding ?? Encoding.UTF8).GetBytes(source), destionation, flush);
  269. }
  270. /// <summary>
  271. /// Represents a proxy.
  272. /// </summary>
  273. public class Proxy
  274. {
  275. /// <summary>
  276. /// Gets or sets the host name.
  277. /// </summary>
  278. /// <value>
  279. /// The host name.
  280. /// </value>
  281. public string Host { get; set; }
  282. /// <summary>
  283. /// Gets or sets the port number.
  284. /// </summary>
  285. /// <value>
  286. /// The port number.
  287. /// </value>
  288. public int Port { get; set; }
  289. /// <summary>
  290. /// Gets or sets the proxy type.
  291. /// </summary>
  292. /// <value>
  293. /// The proxy type.
  294. /// </value>
  295. public ProxyType Type { get; set; }
  296. /// <summary>
  297. /// Gets or sets the name of the user.
  298. /// </summary>
  299. /// <value>
  300. /// The name of the user.
  301. /// </value>
  302. public string UserName { get; set; }
  303. /// <summary>
  304. /// Gets or sets the password.
  305. /// </summary>
  306. /// <value>
  307. /// The password.
  308. /// </value>
  309. public string Password { get; set; }
  310. /// <summary>
  311. /// Parses the proxy URL and creates a new <c>Proxy</c> object.
  312. /// </summary>
  313. /// <param name="uri">The proxy's URI.</param>
  314. /// <returns>
  315. /// The parsed <c>Proxy</c> object.
  316. /// </returns>
  317. public static Proxy ParseUri(Uri uri)
  318. {
  319. var proxy = new Proxy();
  320. proxy.Host = uri.Host;
  321. proxy.Port = uri.Port;
  322. ProxyType type;
  323. if (ProxyType.TryParse(uri.Scheme, true, out type))
  324. {
  325. proxy.Type = type;
  326. }
  327. if (!string.IsNullOrEmpty(uri.UserInfo) && uri.UserInfo.IndexOf(':') != -1)
  328. {
  329. var auth = uri.UserInfo.Split(":".ToCharArray(), 2);
  330. proxy.UserName = auth[0];
  331. proxy.Password = auth[1];
  332. }
  333. return proxy;
  334. }
  335. /// <summary>
  336. /// Creates the proxy from the values within this object.
  337. /// </summary>
  338. /// <returns>
  339. /// The proxy.
  340. /// </returns>
  341. public IProxyClient CreateProxy()
  342. {
  343. var pcf = new ProxyClientFactory();
  344. if (string.IsNullOrWhiteSpace(UserName))
  345. {
  346. return pcf.CreateProxyClient(Type, Host, Port);
  347. }
  348. return pcf.CreateProxyClient(Type, Host, Port, UserName, Password ?? string.Empty);
  349. }
  350. /// <summary>
  351. /// Performs an implicit conversion from <see cref="RoliSoft.TVShowTracker.HttpToSocks.Proxy"/> to <see cref="System.Net.WebProxy"/>.
  352. /// </summary>
  353. /// <param name="proxy">The proxy.</param>
  354. /// <returns>
  355. /// The result of the conversion.
  356. /// </returns>
  357. public static implicit operator WebProxy(Proxy proxy)
  358. {
  359. return new WebProxy(proxy.Host + ":" + proxy.Port);
  360. }
  361. }
  362. }
  363. }