|
|
|
#!/usr/bin/python
|
|
|
|
# -*- coding:utf-8 -*-
|
|
|
|
|
|
|
|
import re
|
|
|
|
from ProxyDecoder import baseFunc
|
|
|
|
|
|
|
|
def __ssPlainDecode(url: str) -> dict or None:
|
|
|
|
"""
|
|
|
|
Shadowsocks原始分享链接解码
|
|
|
|
|
|
|
|
FORMAT: ss://method:password@hostname:port#TAG
|
|
|
|
|
|
|
|
EXAMPLE:
|
|
|
|
ss://bf-cfb:test@192.168.100.1:8888#EXAMPLE
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
content = re.search(r'^ss://([\s\S]+?)(#[\s\S]*)?$', url) # ...#REMARK
|
|
|
|
info = re.search(
|
|
|
|
r'^([\S]+?):([\S]+)@([a-zA-Z0-9.:_-]+):([0-9]+)$',
|
|
|
|
content.group(1) # method:password@server:port
|
|
|
|
)
|
|
|
|
remark = content.group(2)[1:] if content.group(2) is not None else ''
|
|
|
|
remark = remark.replace('+', ' ') # 向后兼容部分客户端
|
|
|
|
return {
|
|
|
|
'server': info[3],
|
|
|
|
'port': int(info[4]),
|
|
|
|
'passwd': info[2],
|
|
|
|
'method': info[1],
|
|
|
|
'remark': baseFunc.urlDecode(remark)
|
|
|
|
}
|
|
|
|
except:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def __ssCommonDecode(url: str) -> dict or None:
|
|
|
|
"""
|
|
|
|
Shadowsocks经典分享链接解码
|
|
|
|
|
|
|
|
FORMAT: ss://BASE64-ENCODED-STRING-WITHOUT-PADDING#TAG
|
|
|
|
|
|
|
|
EXAMPLE:
|
|
|
|
base64('bf-cfb:test/!@#:@192.168.100.1:8888')
|
|
|
|
-> YmYtY2ZiOnRlc3QvIUAjOkAxOTIuMTY4LjEwMC4xOjg4ODg
|
|
|
|
-> ss://YmYtY2ZiOnRlc3QvIUAjOkAxOTIuMTY4LjEwMC4xOjg4ODg#example-server
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
content = re.search(r'^ss://([a-zA-Z0-9_=+\\-]+)#?([\S]*)?$', url) # base64#REMARK
|
|
|
|
info = re.search(
|
|
|
|
r'^([\S]+?):([\S]+)@([a-zA-Z0-9.:_-]+):([0-9]+)$',
|
|
|
|
baseFunc.base64Decode(content.group(1)) # method:password@server:port
|
|
|
|
)
|
|
|
|
return {
|
|
|
|
'server': info[3],
|
|
|
|
'port': int(info[4]),
|
|
|
|
'passwd': info[2],
|
|
|
|
'method': info[1],
|
|
|
|
'remark': baseFunc.urlDecode(content.group(2))
|
|
|
|
}
|
|
|
|
except:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def __sip002Decode(url: str) -> dict or None:
|
|
|
|
"""
|
|
|
|
Shadowsocks SIP002分享链接解码
|
|
|
|
|
|
|
|
FORMAT:
|
|
|
|
SS-URI = "ss://" {userinfo} "@" hostname ":" port [ "/" ] [ "?" plugin ] [ "#" tag ]
|
|
|
|
userinfo = websafe-base64-encode-utf8(method ":" password)
|
|
|
|
|
|
|
|
EXAMPLE:
|
|
|
|
base64('rc4-md5:passwd') -> cmM0LW1kNTpwYXNzd2Q
|
|
|
|
|
|
|
|
obfs-local;obfs=http -> obfs-local%3Bobfs%3Dhttp
|
|
|
|
|
|
|
|
=> ss://cmM0LW1kNTpwYXNzd2Q@192.168.100.1:8888/?plugin=obfs-local%3Bobfs%3Dhttp#Example
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
content = re.search(
|
|
|
|
r'^ss://([a-zA-Z0-9_=+\\-]+)@([a-zA-Z0-9.:_-]+):([0-9]+)'
|
|
|
|
r'/?([\S]*)$', url # base64@server:port/... (/可选)
|
|
|
|
)
|
|
|
|
userInfo = re.search(
|
|
|
|
r'^([\S]+?):([\S]+)$',
|
|
|
|
baseFunc.base64Decode(content.group(1)) # method:password
|
|
|
|
)
|
|
|
|
info = {
|
|
|
|
'server': content.group(2),
|
|
|
|
'port': int(content.group(3)),
|
|
|
|
'passwd': userInfo.group(2),
|
|
|
|
'method': userInfo.group(1),
|
|
|
|
'remark': ''
|
|
|
|
}
|
|
|
|
if content.group(4).find('#') != -1: # ...#REMARK
|
|
|
|
content = re.search(
|
|
|
|
r'^\??([\S]*)#([\S]*)$',
|
|
|
|
content.group(4) # ?...#REMARK (?可选)
|
|
|
|
)
|
|
|
|
info['remark'] = baseFunc.urlDecode(
|
|
|
|
content.group(2)
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
content = re.search(
|
|
|
|
r'^\??([\S]*)$',
|
|
|
|
content.group(4) # ?... (?可选)
|
|
|
|
)
|
|
|
|
plugin = ''
|
|
|
|
for field in content.group(1).split('&'): # /?plugin=...&other1=...&other2=...
|
|
|
|
if field.find('=') == -1: # 缺失xxx=...
|
|
|
|
continue
|
|
|
|
field = re.search(r'^([\S]*?)=([\S]*)$', field) # xxx=...
|
|
|
|
if field.group(1) == 'plugin':
|
|
|
|
plugin = baseFunc.urlDecode(field.group(2)) # plugin参数
|
|
|
|
break
|
|
|
|
if plugin.find(';') == -1: # plugin=... (无参数)
|
|
|
|
pluginField = {
|
|
|
|
'type': plugin,
|
|
|
|
'param': ''
|
|
|
|
}
|
|
|
|
else: # plugin=...;... (带参数)
|
|
|
|
plugin = re.search(r'^([\S]*?);([\S]*)$', plugin) # 插件名;插件参数
|
|
|
|
pluginField = {
|
|
|
|
'type': plugin.group(1),
|
|
|
|
'param': plugin.group(2)
|
|
|
|
}
|
|
|
|
if pluginField['type'] == '': # 无插件情况
|
|
|
|
info['plugin'] = None
|
|
|
|
else:
|
|
|
|
info['plugin'] = pluginField
|
|
|
|
return info
|
|
|
|
except:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def ssDecode(url: str) -> dict or None:
|
|
|
|
"""
|
|
|
|
Shadowsocks分享链接解码
|
|
|
|
|
|
|
|
链接合法:
|
|
|
|
return {
|
|
|
|
'type': 'ss',
|
|
|
|
...
|
|
|
|
}
|
|
|
|
|
|
|
|
链接不合法:
|
|
|
|
return None
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
if url[0:5] != 'ss://':
|
|
|
|
return None
|
|
|
|
result = __ssCommonDecode(url) # try shadowsocks common decode
|
|
|
|
if result is None:
|
|
|
|
result = __sip002Decode(url) # try shadowsocks sip002 decode
|
|
|
|
if result is None:
|
|
|
|
result = __ssPlainDecode(url) # try shadowsocks plain decode
|
|
|
|
if result is not None: # 解析成功
|
|
|
|
result['type'] = 'ss'
|
|
|
|
return result
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
return None
|