Files
shadowsocks-windows/shadowsocks-csharp/Controller/PACServer.cs
T

324 lines
11 KiB
C#
Executable File

using Shadowsocks.Model;
using Shadowsocks.Properties;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
namespace Shadowsocks.Controller
{
class PACServer
{
private static int PORT = 8093;
private static string PAC_FILE = "pac.txt";
private static Configuration config;
Socket _listener;
FileSystemWatcher watcher;
GfwListUpdater gfwlistUpdater;
public event EventHandler PACFileChanged;
public void Start(Configuration configuration)
{
try
{
config = configuration;
// Create a TCP/IP socket.
_listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
IPEndPoint localEndPoint = null;
if (configuration.shareOverLan)
{
localEndPoint = new IPEndPoint(IPAddress.Any, PORT);
}
else
{
localEndPoint = new IPEndPoint(IPAddress.Loopback, PORT);
}
// Bind the socket to the local endpoint and listen for incoming connections.
_listener.Bind(localEndPoint);
_listener.Listen(100);
_listener.BeginAccept(
new AsyncCallback(AcceptCallback),
_listener);
WatchPacFile();
StartGfwListUpdater();
}
catch (SocketException)
{
_listener.Close();
throw;
}
}
public void Stop()
{
if (gfwlistUpdater != null)
{
gfwlistUpdater.Stop();
gfwlistUpdater = null;
}
if (_listener != null)
{
_listener.Close();
_listener = null;
}
}
public string TouchPACFile()
{
if (File.Exists(PAC_FILE))
{
return PAC_FILE;
}
else
{
FileManager.UncompressFile(PAC_FILE, Resources.proxy_pac_txt);
return PAC_FILE;
}
}
// we don't even use it
static byte[] requestBuf = new byte[2048];
public void AcceptCallback(IAsyncResult ar)
{
Socket listener = (Socket)ar.AsyncState;
try
{
Socket conn = listener.EndAccept(ar);
object[] state = new object[] {
conn,
requestBuf
};
conn.BeginReceive(requestBuf, 0, requestBuf.Length, 0,
new AsyncCallback(ReceiveCallback), state);
}
catch (ObjectDisposedException)
{
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
try
{
listener.BeginAccept(
new AsyncCallback(AcceptCallback),
listener);
}
catch (ObjectDisposedException)
{
// do nothing
}
catch (Exception e)
{
Logging.LogUsefulException(e);
}
}
}
private string GetPACContent()
{
if (File.Exists(PAC_FILE))
{
return File.ReadAllText(PAC_FILE, Encoding.UTF8);
}
else
{
byte[] pacGZ = Resources.proxy_pac_txt;
byte[] buffer = new byte[1024]; // builtin pac gzip size: maximum 100K
MemoryStream sb = new MemoryStream();
int n;
using (GZipStream input = new GZipStream(new MemoryStream(pacGZ),
CompressionMode.Decompress, false))
{
while((n = input.Read(buffer, 0, buffer.Length)) > 0)
{
sb.Write(buffer, 0, n);
}
return System.Text.Encoding.UTF8.GetString(sb.ToArray());
}
}
}
private void ReceiveCallback(IAsyncResult ar)
{
object[] state = (object[])ar.AsyncState;
Socket conn = (Socket)state[0];
byte[] requestBuf = (byte[])state[1];
try
{
int bytesRead = conn.EndReceive(ar);
string pac = GetPACContent();
IPEndPoint localEndPoint = (IPEndPoint)conn.LocalEndPoint;
string proxy = GetPACAddress(requestBuf, localEndPoint);
pac = pac.Replace("__PROXY__", proxy);
if (bytesRead > 0)
{
string text = String.Format(@"HTTP/1.1 200 OK
Server: Shadowsocks
Content-Type: application/x-ns-proxy-autoconfig
Content-Length: {0}
Connection: Close
", System.Text.Encoding.UTF8.GetBytes(pac).Length) + pac;
byte[] response = System.Text.Encoding.UTF8.GetBytes(text);
conn.BeginSend(response, 0, response.Length, 0, new AsyncCallback(SendCallback), conn);
Util.Util.ReleaseMemory();
}
else
{
conn.Close();
}
}
catch (Exception e)
{
Console.WriteLine(e);
conn.Close();
}
}
private void SendCallback(IAsyncResult ar)
{
Socket conn = (Socket)ar.AsyncState;
try
{
conn.Shutdown(SocketShutdown.Send);
}
catch
{ }
}
private void WatchPacFile()
{
if (watcher != null)
{
watcher.Dispose();
}
watcher = new FileSystemWatcher(Directory.GetCurrentDirectory());
watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
watcher.Filter = PAC_FILE;
watcher.Changed += Watcher_Changed;
watcher.Created += Watcher_Changed;
watcher.Deleted += Watcher_Changed;
watcher.Renamed += Watcher_Changed;
watcher.EnableRaisingEvents = true;
}
private void Watcher_Changed(object sender, FileSystemEventArgs e)
{
if (PACFileChanged != null)
{
PACFileChanged(this, new EventArgs());
}
}
private string GetPACAddress(byte[] requestBuf, IPEndPoint localEndPoint)
{
string proxy = "PROXY " + localEndPoint.Address + ":8123;";
//try
//{
// string requestString = Encoding.UTF8.GetString(requestBuf);
// if (requestString.IndexOf("AppleWebKit") >= 0)
// {
// string address = "" + localEndPoint.Address + ":" + config.GetCurrentServer().local_port;
// proxy = "SOCKS5 " + address + "; SOCKS " + address + ";";
// }
//}
//catch (Exception e)
//{
// Console.WriteLine(e);
//}
return proxy;
}
private void StartGfwListUpdater()
{
if (gfwlistUpdater != null)
{
gfwlistUpdater.Stop();
gfwlistUpdater = null;
}
gfwlistUpdater = new GfwListUpdater();
gfwlistUpdater.GfwListChanged += gfwlistUpdater_GfwListChanged;
IPEndPoint localEndPoint = (IPEndPoint)_listener.LocalEndPoint;
gfwlistUpdater.proxy = new WebProxy(localEndPoint.Address.ToString(), 8123);
gfwlistUpdater.useSystemProxy = false;
/* Delay 30 seconds, wait proxy start up. */
gfwlistUpdater.ScheduleUpdateTime(30);
gfwlistUpdater.Start();
}
private void gfwlistUpdater_GfwListChanged(object sender, GfwListUpdater.GfwListChangedArgs e)
{
if (e.GfwList == null || e.GfwList.Length == 0) return;
string pacfile = TouchPACFile();
string pacContent = File.ReadAllText(pacfile);
string oldDomains;
if (ClearPacContent(ref pacContent, out oldDomains))
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("{");
for (int i = 0; i < e.GfwList.Length; i++)
{
if (i == e.GfwList.Length - 1)
sb.AppendFormat("\t\"{0}\": {1}\r\n", e.GfwList[i], 1);
else
sb.AppendFormat("\t\"{0}\": {1},\r\n", e.GfwList[i], 1);
}
sb.Append("}");
string newDomains = sb.ToString();
if (!string.Equals(oldDomains, newDomains))
{
pacContent = pacContent.Replace("__LAST_MODIFIED__", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
pacContent = pacContent.Replace("__DOMAINS__", newDomains);
File.WriteAllText(pacfile, pacContent);
Console.WriteLine("gfwlist updated - " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
}
}
else
{
Console.WriteLine("Broken pac file.");
}
}
private bool ClearPacContent(ref string pacContent, out string oldDomains)
{
Regex regex = new Regex("(/\\*.*?\\*/\\s*)?var\\s+domains\\s*=\\s*(\\{(\\s*\"[^\"]*\"\\s*:\\s*\\d+\\s*,)*\\s*(\\s*\"[^\"]*\"\\s*:\\s*\\d+\\s*)\\})", RegexOptions.Singleline);
Match m = regex.Match(pacContent);
if (m.Success)
{
oldDomains = m.Result("$2");
pacContent = regex.Replace(pacContent, "/* Last Modified: __LAST_MODIFIED__ */\r\nvar domains = __DOMAINS__");
return true;
}
oldDomains = null;
return false;
}
}
}