#!/usr/bin/python # -*- coding:utf-8 -*- import re from ProxyDecoder import baseFunc def __ssPlainDecode(url: str) -> dict: """ Shadowsocks原始分享链接解码 FORMAT: ss://method:password@hostname:port#TAG EXAMPLE: ss://bf-cfb:test@192.168.100.1:8888#EXAMPLE """ remark = '' match = re.search(r'^ss://([\S]+)$', url) # ss://... if match[1].find('#') != -1: # ...#REMARK match = re.search(r'^([\S]+)#([\S]*)$', match[1]) remark = baseFunc.urlDecode( match[2] if match[2] is not None else '' ) match = re.search( r'^([\S]+?):([\S]+)@([a-zA-Z0-9.:\-_\[\]]+):([0-9]+)$', match[1] # method:password@server:port ) return { 'server': baseFunc.formatHost(match[3]), 'port': int(match[4]), 'passwd': match[2], 'method': match[1], 'remark': remark } def __ssCommonDecode(url: str) -> dict: """ Shadowsocks经典分享链接解码 FORMAT: ss://BASE64-ENCODED-STRING-WITHOUT-PADDING#TAG EXAMPLE: base64('bf-cfb:test/!@#:@192.168.100.1:8888') -> YmYtY2ZiOnRlc3QvIUAjOkAxOTIuMTY4LjEwMC4xOjg4ODg -> ss://YmYtY2ZiOnRlc3QvIUAjOkAxOTIuMTY4LjEwMC4xOjg4ODg#example-server """ match = re.search(r'^ss://([a-zA-Z0-9\-_+\\=]+)#?([\S]*)?$', url) # base64#REMARK remark = baseFunc.urlDecode( match[2] if match[2] is not None else '' ) match = re.search( r'^([\S]+?):([\S]+)@([a-zA-Z0-9.:\-_\[\]]+):([0-9]+)$', baseFunc.base64Decode(match[1]) # method:password@server:port ) return { 'server': baseFunc.formatHost(match[3]), 'port': int(match[4]), 'passwd': match[2], 'method': match[1], 'remark': remark } def __sip002Decode(url: str) -> dict: """ 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 """ match = 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(match[1]) # method:password ) info = { 'server': baseFunc.formatHost(match[2]), 'port': int(match[3]), 'passwd': userInfo[2], 'method': userInfo[1], 'remark': '' } if match[4].find('#') != -1: # ...#REMARK match = re.search( r'^\??([\S]*)#([\S]*)$', match[4] # ?...#REMARK (?可选) ) info['remark'] = baseFunc.urlDecode(match[2]) else: match = re.search( r'^\??([\S]*)$', match[4] # ?... (?可选) ) params = baseFunc.paramSplit(match[1]) # /?plugin=...&other1=...&other2=... pluginField = params['plugin'] if 'plugin' in params else '' if pluginField.find(';') == -1: # plugin=... (无参数) pluginObject = { 'type': pluginField, 'param': '' } else: # plugin=...;... (带参数) match = re.search(r'^([\S]*?);([\S]*)$', pluginField) # 插件名;插件参数 pluginObject = { 'type': match[1], 'param': match[2] } if pluginObject['type'] != '': # 带插件时配置 info['plugin'] = pluginObject return info def decode(url: str, compatible: bool = False) -> dict or None: if url.split('://')[0] != 'ss': raise Exception('Unexpected scheme') try: ssInfo = __ssCommonDecode(url) # try shadowsocks common decode except: try: ssInfo = __sip002Decode(url) # try shadowsocks sip002 decode except: try: ssInfo = __ssPlainDecode(url) # try shadowsocks plain decode except: raise Exception('Url could not be parsed') if compatible and 'remark' in ssInfo: # 向后兼容部分客户端 ssInfo['remark'] = ssInfo['remark'].replace('+', ' ') return { 'type': 'ss', 'info': ssInfo, }