Skip to content

Commit 3d74eb7

Browse files
authored
Add files via upload
1 parent 7b1d90e commit 3d74eb7

File tree

3 files changed

+52
-29
lines changed

3 files changed

+52
-29
lines changed

pproxy/__init__.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from pproxy import proto
33

44
__title__ = 'pproxy'
5-
__version__ = "0.9.4"
5+
__version__ = "0.9.6"
66
__description__ = "Proxy server that can tunnel among remote servers by regex rules."
77
__author__ = "Qian Wenjie"
88
__license__ = "MIT License"
@@ -15,12 +15,12 @@
1515
asyncio.StreamReader.read_n = lambda self, n: asyncio.wait_for(self.readexactly(n), timeout=SOCKET_TIMEOUT)
1616
asyncio.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, pac, pactext, unix_path, verbose=DUMMY, modstat=lambda r,h:lambda i:DUMMY, **kwargs):
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):
1919
try:
2020
remote_ip = writer.get_extra_info('peername')[0] if not unix_path else None
21-
reader_cipher = (await cipher(reader, writer))[0] if cipher else None
21+
reader_cipher = cipher(reader, writer)[0] if cipher else None
2222
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, pac=pac, pactext=pactext, reader_cipher=reader_cipher)
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)
2424
if host_name is None:
2525
writer.close()
2626
return
@@ -44,7 +44,7 @@ async def proxy_handler(reader, writer, protos, auth, rserver, block, auth_table
4444
raise Exception(f'Connection timeout {rserver}')
4545
try:
4646
if viaproxy:
47-
writer_cipher_r = (await roption.cipher(reader_remote, writer_remote))[1] if roption.cipher else None
47+
writer_cipher_r = roption.cipher(reader_remote, writer_remote)[1] if roption.cipher else None
4848
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)
4949
else:
5050
writer_remote.write(initbuf)
@@ -60,8 +60,8 @@ async def proxy_handler(reader, writer, protos, auth, rserver, block, auth_table
6060
try: writer.close()
6161
except Exception: pass
6262

63-
def pattern_compile(file_name):
64-
with open(file_name) as f:
63+
def pattern_compile(filename):
64+
with open(filename) as f:
6565
return re.compile('|'.join(i.strip() for i in f if i.strip() and not i.startswith('#'))).fullmatch
6666

6767
def uri_compile(uri):
@@ -100,7 +100,8 @@ def main():
100100
parser.add_argument('-b', dest='block', type=pattern_compile, help='block regex rules')
101101
parser.add_argument('-v', dest='v', action='store_true', help='print verbose output')
102102
parser.add_argument('--ssl', dest='sslfile', help='certfile[,keyfile] if server listen in ssl mode')
103-
parser.add_argument('--pac', dest='pac', help='http pac file path')
103+
parser.add_argument('--pac', dest='pac', help='http PAC path')
104+
parser.add_argument('--get', dest='gets', default=[], action='append', help='http custom path/file')
104105
parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}')
105106
args = parser.parse_args()
106107
if not args.listen:
@@ -110,13 +111,18 @@ def main():
110111
args.auth_tables = pickle.load(f)
111112
else:
112113
args.auth_tables = {}
114+
args.httpget = {}
113115
if args.pac:
114116
pactext = 'function FindProxyForURL(u,h){' + (f'var b=/^(:?{args.block.__self__.pattern})$/i;if(b.test(h))return "";' if args.block else '')
115117
for i, option in enumerate(args.rserver):
116118
pactext += (f'var m{i}=/^(:?{option.match.__self__.pattern})$/i;if(m{i}.test(h))' if option.match else '') + f'return "PROXY %(host)s";'
117-
args.pactext = (pactext+'return "DIRECT";}', 'function FindProxyForURL(u,h){{return "PROXY %(host)s";}}', 'function FindProxyForURL(u,h){return "DIRECT";}')
118-
else:
119-
args.pactext = None
119+
args.httpget[args.pac] = pactext+'return "DIRECT";}'
120+
args.httpget[args.pac+'/all'] = 'function FindProxyForURL(u,h){return "PROXY %(host)s";}'
121+
args.httpget[args.pac+'/none'] = 'function FindProxyForURL(u,h){return "DIRECT";}'
122+
for gets in args.gets:
123+
path, filename = gets.split(',', 1)
124+
with open(filename, 'r') as f:
125+
args.httpget[path] = f.read()
120126
if args.sslfile:
121127
sslfile = args.sslfile.split(',')
122128
for option in args.listen:

pproxy/ciphers.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
class BaseCipher(object):
77
CACHE = {}
8-
def __init__(self, key, iv=None, ota=False):
8+
def __init__(self, key, ota=False):
99
if self.KEY_LENGTH > 0:
1010
self.key = self.CACHE.get(b'key'+key)
1111
if self.key is None:
@@ -15,8 +15,10 @@ def __init__(self, key, iv=None, ota=False):
1515
self.key = self.CACHE[b'key'+key] = b''.join(keybuf)[:self.KEY_LENGTH]
1616
else:
1717
self.key = key
18-
self.iv = os.urandom(self.IV_LENGTH) if iv is None else iv
1918
self.ota = ota
19+
self.iv = None
20+
def setup_iv(self, iv=None):
21+
self.iv = os.urandom(self.IV_LENGTH) if iv is None else iv
2022
self.setup()
2123
def decrypt(self, s):
2224
return self.cipher.decrypt(s)
@@ -143,13 +145,27 @@ def get_cipher(cipher_key):
143145
if cipher not in MAPPINGS:
144146
raise argparse.ArgumentTypeError(f'existing ciphers: {list(MAPPINGS.keys())}')
145147
cipher, key, ota = MAPPINGS[cipher], key.encode(), bool(ota) if ota else False
146-
async def apply_cipher(reader, writer):
147-
writer_cipher = cipher(key, ota=ota)
148-
writer.write(writer_cipher.iv)
149-
writer.write = lambda s, o=writer.write, p=writer_cipher.encrypt: o(p(s))
150-
reader_cipher = cipher(key, await reader.read_n(len(writer_cipher.iv)), ota=ota)
151-
reader._buffer = bytearray(reader_cipher.decrypt(bytes(reader._buffer)))
152-
reader.feed_data = lambda s, o=reader.feed_data, p=reader_cipher.decrypt: o(p(s))
148+
def apply_cipher(reader, writer):
149+
reader_cipher, writer_cipher = cipher(key, ota=ota), cipher(key, ota=ota)
150+
def feed_data(s, o=reader.feed_data):
151+
if not reader_cipher.iv:
152+
s = bytes(reader._buffer + s)
153+
if len(s) >= reader_cipher.IV_LENGTH:
154+
reader_cipher.setup_iv(s[:reader_cipher.IV_LENGTH])
155+
s = reader_cipher.decrypt(s[reader_cipher.IV_LENGTH:])
156+
reader._buffer.clear()
157+
else:
158+
s = reader_cipher.decrypt(s)
159+
return o(s)
160+
def write(s, o=writer.write):
161+
if not s:
162+
return
163+
if not writer_cipher.iv:
164+
writer_cipher.setup_iv()
165+
o(writer_cipher.iv)
166+
return o(writer_cipher.encrypt(s))
167+
reader.feed_data = feed_data
168+
writer.write = write
153169
return reader_cipher, writer_cipher
154170
apply_cipher.ota = ota
155171
return apply_cipher

pproxy/proto.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -115,18 +115,21 @@ def correct_header(header, **kw):
115115
return header.isalpha()
116116

117117
@staticmethod
118-
async def parse(header, reader, writer, auth, auth_tables, remote_ip, pac, pactext, **kw):
118+
async def parse(header, reader, writer, auth, auth_tables, remote_ip, httpget, **kw):
119119
lines = header + await reader.read_until(b'\r\n\r\n')
120120
headers = lines[:-4].decode().split('\r\n')
121121
method, path, ver = HTTP_LINE.fullmatch(headers.pop(0)).groups()
122122
lines = '\r\n'.join(i for i in headers if not i.startswith('Proxy-'))
123123
headers = dict(i.split(': ', 1) for i in headers if ': ' in i)
124-
if method == 'GET' and pac and path.startswith(pac):
125-
auth_tables[remote_ip] = time.time()
126-
pacidx = 1 if 'all' in path else (2 if 'none' in path else 0)
127-
text = (pactext[pacidx] % dict(host=headers["Host"])).encode()
128-
writer.write(f'{ver} 200 OK\r\nConnection: close\r\nContent-Type: application/x-ns-proxy-autoconfig\r\nCache-Control: max-age=900\r\nContent-Length: {len(text)}\r\n\r\n'.encode() + text)
129-
return None, None, None
124+
url = urllib.parse.urlparse(path)
125+
if method == 'GET' and not url.hostname:
126+
for path, text in httpget.items():
127+
if url.path == path:
128+
auth_tables[remote_ip] = time.time()
129+
text = (text % dict(host=headers["Host"])).encode()
130+
writer.write(f'{ver} 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\nCache-Control: max-age=900\r\nContent-Length: {len(text)}\r\n\r\n'.encode() + text)
131+
return None, None, None
132+
raise Exception(f'404 {method} {path}')
130133
if auth:
131134
pauth = headers.get('Proxy-Authorization', None)
132135
httpauth = 'Basic ' + base64.b64encode(auth).decode()
@@ -142,8 +145,6 @@ async def parse(header, reader, writer, auth, auth_tables, remote_ip, pac, pacte
142145
else:
143146
url = urllib.parse.urlparse(path)
144147
host_name = url.hostname
145-
if not host_name:
146-
raise Exception(f'404 {method} {path}')
147148
port = url.port or 80
148149
newpath = url._replace(netloc='', scheme='').geturl()
149150
return host_name, port, f'{method} {newpath} {ver}\r\n{lines}\r\n\r\n'.encode()

0 commit comments

Comments
 (0)