1- import argparse , time , re , pickle , asyncio , functools , types , os , urllib .parse
1+ import argparse , time , re , asyncio , functools , types , urllib .parse
22from pproxy import proto
33
44__title__ = 'pproxy'
5- __version__ = "1.0 .0"
5+ __version__ = "1.2 .0"
66__description__ = "Proxy server that can tunnel among remote servers by regex rules."
77__author__ = "Qian Wenjie"
88__license__ = "MIT License"
1515asyncio .StreamReader .read_n = lambda self , n : asyncio .wait_for (self .readexactly (n ), timeout = SOCKET_TIMEOUT )
1616asyncio .StreamReader .read_until = lambda self , s : asyncio .wait_for (self .readuntil (s ), timeout = SOCKET_TIMEOUT )
1717
18- async def proxy_handler (reader , writer , protos , auth , rserver , block , auth_tables , cipher , httpget , unix_path , verbose = DUMMY , modstat = lambda r ,h :lambda i :DUMMY , ** kwargs ):
18+ if not hasattr (asyncio .StreamReader , 'readuntil' ): # Python 3.4 and below
19+ @asyncio .coroutine
20+ def readuntil (self , separator ):
21+ seplen = len (separator )
22+ offset = 0
23+ while True :
24+ buflen = len (self ._buffer )
25+ if buflen - offset >= seplen :
26+ isep = self ._buffer .find (separator , offset )
27+ if isep != - 1 :
28+ break
29+ offset = buflen + 1 - seplen
30+ if self ._eof :
31+ chunk = bytes (self ._buffer )
32+ self ._buffer .clear ()
33+ raise asyncio .IncompleteReadError (chunk , None )
34+ yield from self ._wait_for_data ('readuntil' )
35+ chunk = self ._buffer [:isep + seplen ]
36+ del self ._buffer [:isep + seplen ]
37+ self ._maybe_resume_transport ()
38+ return bytes (chunk )
39+ asyncio .StreamReader .readuntil = readuntil
40+
41+ def proxy_handler (reader , writer , protos , auth , rserver , block , auth_tables , cipher , httpget , unix_path , verbose = DUMMY , modstat = lambda r ,h :lambda i :DUMMY , ** kwargs ):
1942 try :
2043 remote_ip = writer .get_extra_info ('peername' )[0 ] if not unix_path else None
2144 reader_cipher = cipher (reader , writer )[0 ] if cipher else None
22- header = await reader .read_n (1 )
23- lproto , host_name , port , initbuf = await proto .parse (protos , reader = reader , writer = writer , header = header , auth = auth , auth_tables = auth_tables , remote_ip = remote_ip , httpget = httpget , reader_cipher = reader_cipher )
45+ header = yield from reader .read_n (1 )
46+ lproto , host_name , port , initbuf = yield from proto .parse (protos , reader = reader , writer = writer , header = header , auth = auth , auth_tables = auth_tables , remote_ip = remote_ip , httpget = httpget , reader_cipher = reader_cipher )
2447 if host_name is None :
2548 writer .close ()
2649 return
@@ -33,36 +56,36 @@ async def proxy_handler(reader, writer, protos, auth, rserver, block, auth_table
3356 break
3457 viaproxy = bool (roption )
3558 if viaproxy :
36- verbose (f' { lproto .__name__ } { host_name } :{ port } -> { roption .protos [0 ].__name__ } { roption .bind } ' )
59+ verbose ('{l .__name__} {}:{} -> {r .protos[0].__name__} {r .bind}'. format ( host_name , port , l = lproto , r = roption ) )
3760 connect = roption .connect
3861 else :
39- verbose (f' { lproto .__name__ } { host_name } :{ port } ' )
62+ verbose ('{l .__name__} {}:{}' . format ( host_name , port , l = lproto ) )
4063 connect = functools .partial (asyncio .open_connection , host = host_name , port = port )
4164 try :
42- reader_remote , writer_remote = await asyncio .wait_for (connect (), timeout = SOCKET_TIMEOUT )
65+ reader_remote , writer_remote = yield from asyncio .wait_for (connect (), timeout = SOCKET_TIMEOUT )
4366 except asyncio .TimeoutError :
44- raise Exception (f 'Connection timeout { rserver } ' )
67+ raise Exception ('Connection timeout {}' . format ( rserver ) )
4568 try :
4669 if viaproxy :
4770 writer_cipher_r = roption .cipher (reader_remote , writer_remote )[1 ] if roption .cipher else None
48- await roption .protos [0 ].connect (reader_remote = reader_remote , writer_remote = writer_remote , rauth = roption .auth , host_name = host_name , port = port , initbuf = initbuf , writer_cipher_r = writer_cipher_r )
71+ yield from roption .protos [0 ].connect (reader_remote = reader_remote , writer_remote = writer_remote , rauth = roption .auth , host_name = host_name , port = port , initbuf = initbuf , writer_cipher_r = writer_cipher_r )
4972 else :
5073 writer_remote .write (initbuf )
5174 except Exception :
5275 writer_remote .close ()
5376 raise Exception ('Unknown remote protocol' )
5477 m = modstat (remote_ip , host_name )
55- asyncio .ensure_future (proto .base .channel (reader_remote , writer , m (2 + viaproxy ), m (4 + viaproxy )))
56- asyncio .ensure_future (lproto .channel (reader , writer_remote , m (viaproxy ), DUMMY ))
78+ asyncio .async (proto .base .channel (reader_remote , writer , m (2 + viaproxy ), m (4 + viaproxy )))
79+ asyncio .async (lproto .channel (reader , writer_remote , m (viaproxy ), DUMMY ))
5780 except Exception as ex :
5881 if not isinstance (ex , asyncio .TimeoutError ):
59- verbose (f' { str (ex ) or "Unsupported protocol" } from { remote_ip } ' )
82+ verbose ('{} from {}' . format ( str (ex ) or "Unsupported protocol" , remote_ip ) )
6083 try : writer .close ()
6184 except Exception : pass
6285
6386def pattern_compile (filename ):
6487 with open (filename ) as f :
65- return re .compile ('|' .join (i .strip () for i in f if i .strip () and not i .startswith ('#' ))). fullmatch
88+ return re .compile ('(:?' + '' . join ( ' |' .join (i .strip () for i in f if i .strip () and not i .startswith ('#' )))+ ')$' ). match
6689
6790def uri_compile (uri ):
6891 url = urllib .parse .urlparse (uri )
@@ -102,20 +125,16 @@ def main():
102125 parser .add_argument ('--ssl' , dest = 'sslfile' , help = 'certfile[,keyfile] if server listen in ssl mode' )
103126 parser .add_argument ('--pac' , dest = 'pac' , help = 'http PAC path' )
104127 parser .add_argument ('--get' , dest = 'gets' , default = [], action = 'append' , help = 'http custom path/file' )
105- parser .add_argument ('--version' , action = 'version' , version = f '%(prog)s { __version__ } ' )
128+ parser .add_argument ('--version' , action = 'version' , version = '%(prog)s {}' . format ( __version__ ) )
106129 args = parser .parse_args ()
107130 if not args .listen :
108131 args .listen .append (uri_compile ('http+socks://:8080/' ))
109- if os .path .exists ('.auth_tables' ):
110- with open ('.auth_tables' , 'rb' ) as f :
111- args .auth_tables = pickle .load (f )
112- else :
113- args .auth_tables = {}
132+ args .auth_tables = {}
114133 args .httpget = {}
115134 if args .pac :
116- pactext = 'function FindProxyForURL(u,h){' + (f 'var b=/^(:?{ args . block . __self__ . pattern } )$/i;if(b.test(h))return "";' if args .block else '' )
135+ pactext = 'function FindProxyForURL(u,h){' + ('var b=/^(:?{})$/i;if(b.test(h))return "";' . format ( args . block . __self__ . pattern ) if args .block else '' )
117136 for i , option in enumerate (args .rserver ):
118- pactext += (f 'var m{ i } =/^(:?{ option . match . __self__ . pattern } )$/i;if(m{ i } .test(h))' if option .match else '' ) + f 'return "PROXY %(host)s";'
137+ pactext += ('var m{1 }=/^(:?{0 })$/i;if(m{1 }.test(h))' . format ( option . match . __self__ . pattern , i ) if option .match else '' ) + 'return "PROXY %(host)s";'
119138 args .httpget [args .pac ] = pactext + 'return "DIRECT";}'
120139 args .httpget [args .pac + '/all' ] = 'function FindProxyForURL(u,h){return "PROXY %(host)s";}'
121140 args .httpget [args .pac + '/none' ] = 'function FindProxyForURL(u,h){return "DIRECT";}'
@@ -130,29 +149,26 @@ def main():
130149 option .sslclient .load_cert_chain (* sslfile )
131150 option .sslserver .load_cert_chain (* sslfile )
132151 elif any (map (lambda o : o .sslclient , args .listen )):
133- print (f 'You must specify --ssl to listen in ssl mode' )
152+ print ('You must specify --ssl to listen in ssl mode' )
134153 return
135154 loop = asyncio .get_event_loop ()
136155 if args .v :
137156 from pproxy import verbose
138157 verbose .setup (loop , args )
139158 servers = []
140159 for option in args .listen :
141- print (f 'Serving on { option .bind } by { "," .join (i .__name__ for i in option .protos )} ' , '(SSL)' if option .sslclient else '' )
142- handler = functools .partial (proxy_handler , ** vars (args ), ** vars (option ))
160+ print ('Serving on' , option .bind , 'by' , "," .join (i .__name__ for i in option .protos ) + ( '(SSL)' if option .sslclient else '' ), '({})' . format ( option . cipher . name ) if option . cipher else '' )
161+ handler = functools .partial (functools . partial ( proxy_handler , ** vars (args ) ), ** vars (option ))
143162 try :
144163 server = loop .run_until_complete (option .server (handler ))
145164 servers .append (server )
146165 except Exception as ex :
147- print (f 'Start server failed.\n \t ==> { ex } ' )
166+ print ('Start server failed.\n \t ==>' , ex )
148167 if servers :
149168 try :
150169 loop .run_forever ()
151170 except KeyboardInterrupt :
152171 print ('exit' )
153- if args .auth_tables :
154- with open ('.auth_tables' , 'wb' ) as f :
155- pickle .dump (args .auth_tables , f , pickle .HIGHEST_PROTOCOL )
156172 for task in asyncio .Task .all_tasks ():
157173 task .cancel ()
158174 for server in servers :
0 commit comments